Catalyst-Plugin-Session-0.43/000755 000000 000000 00000000000 14246413534 016206 5ustar00rootwheel000000 000000 Catalyst-Plugin-Session-0.43/README000644 000000 000000 00000037247 14246413534 017103 0ustar00rootwheel000000 000000 NAME Catalyst::Plugin::Session - Generic Session plugin - ties together server side storage and client side state required to maintain session data. SYNOPSIS # To get sessions to "just work", all you need to do is use these plugins: use Catalyst qw/ Session Session::Store::FastMmap Session::State::Cookie /; # you can replace Store::FastMmap with Store::File - both have sensible # default configurations (see their docs for details) # more complicated backends are available for other scenarios (DBI storage, # etc) # after you've loaded the plugins you can save session data # For example, if you are writing a shopping cart, it could be implemented # like this: sub add_item : Local { my ( $self, $c ) = @_; my $item_id = $c->req->param("item"); # $c->session is a hash ref, a bit like $c->stash # the difference is that it' preserved across requests push @{ $c->session->{items} }, $item_id; $c->forward("MyView"); } sub display_items : Local { my ( $self, $c ) = @_; # values in $c->session are restored $c->stash->{items_to_display} = [ map { MyModel->retrieve($_) } @{ $c->session->{items} } ]; $c->forward("MyView"); } DESCRIPTION The Session plugin is the base of two related parts of functionality required for session management in web applications. The first part, the State, is getting the browser to repeat back a session key, so that the web application can identify the client and logically string several requests together into a session. The second part, the Store, deals with the actual storage of information about the client. This data is stored so that the it may be revived for every request made by the same client. This plugin links the two pieces together. RECOMENDED BACKENDS Session::State::Cookie The only really sane way to do state is using cookies. Session::Store::File A portable backend, based on Cache::File. Session::Store::FastMmap A fast and flexible backend, based on Cache::FastMmap. METHODS sessionid An accessor for the session ID value. session Returns a hash reference that might contain unserialized values from previous requests in the same session, and whose modified value will be saved for future requests. This method will automatically create a new session and session ID if none exists. You can also set session keys by passing a list of key/value pairs or a hashref. $c->session->{foo} = "bar"; # This works. $c->session(one => 1, two => 2); # And this. $c->session({ answer => 42 }); # And this. session_expires This method returns the time when the current session will expire, or 0 if there is no current session. If there is a session and it already expired, it will delete the session and return 0 as well. flash This is like Ruby on Rails' flash data structure. Think of it as a stash that lasts for longer than one request, letting you redirect instead of forward. The flash data will be cleaned up only on requests on which actually use $c->flash (thus allowing multiple redirections), and the policy is to delete all the keys which haven't changed since the flash data was loaded at the end of every request. Note that use of the flash is an easy way to get data across requests, but it's also strongly disrecommended, due it it being inherently plagued with race conditions. This means that it's unlikely to work well if your users have multiple tabs open at once, or if your site does a lot of AJAX requests. Catalyst::Plugin::StatusMessage is the recommended alternative solution, as this doesn't suffer from these issues. sub moose : Local { my ( $self, $c ) = @_; $c->flash->{beans} = 10; $c->response->redirect( $c->uri_for("foo") ); } sub foo : Local { my ( $self, $c ) = @_; my $value = $c->flash->{beans}; # ... $c->response->redirect( $c->uri_for("bar") ); } sub bar : Local { my ( $self, $c ) = @_; if ( exists $c->flash->{beans} ) { # false } } clear_flash Zap all the keys in the flash regardless of their current state. keep_flash @keys If you want to keep a flash key for the next request too, even if it hasn't changed, call "keep_flash" and pass in the keys as arguments. delete_session REASON This method is used to invalidate a session. It takes an optional parameter which will be saved in "session_delete_reason" if provided. NOTE: This method will also delete your flash data. session_delete_reason This accessor contains a string with the reason a session was deleted. Possible values include: * "address mismatch" * "session expired" session_expire_key $key, $ttl Mark a key to expire at a certain time (only useful when shorter than the expiry time for the whole session). For example: __PACKAGE__->config('Plugin::Session' => { expires => 10000000000 }); # "forever" (NB If this number is too large, Y2K38 breakage could result.) # later $c->session_expire_key( __user => 3600 ); Will make the session data survive, but the user will still be logged out after an hour. Note that these values are not auto extended. change_session_id By calling this method you can force a session id change while keeping all session data. This method might come handy when you are paranoid about some advanced variations of session fixation attack. If you want to prevent this session fixation scenario: 0) let us have WebApp with anonymous and authenticated parts 1) a hacker goes to vulnerable WebApp and gets a real sessionid, just by browsing anonymous part of WebApp 2) the hacker inserts (somehow) this values into a cookie in victim's browser 3) after the victim logs into WebApp the hacker can enter his/her session you should call change_session_id in your login controller like this: if ($c->authenticate( { username => $user, password => $pass } )) { # login OK $c->change_session_id; ... } else { # login FAILED ... } change_session_expires $expires You can change the session expiration time for this session; $c->change_session_expires( 4000 ); Note that this only works to set the session longer than the config setting. INTERNAL METHODS setup This method is extended to also make calls to "check_session_plugin_requirements" and "setup_session". check_session_plugin_requirements This method ensures that a State and a Store plugin are also in use by the application. setup_session This method populates "$c->config('Plugin::Session')" with the default values listed in "CONFIGURATION". prepare_action This method is extended. Its only effect is if the (off by default) "flash_to_stash" configuration parameter is on - then it will copy the contents of the flash to the stash at prepare time. finalize_headers This method is extended and will extend the expiry time before sending the response. finalize_body This method is extended and will call finalize_session before the other finalize_body methods run. Here we persist the session data if a session exists. initialize_session_data This method will initialize the internal structure of the session, and is called by the "session" method if appropriate. create_session_id Creates a new session ID using "generate_session_id" if there is no session ID yet. validate_session_id SID Make sure a session ID is of the right format. This currently ensures that the session ID string is any amount of case insensitive hexadecimal characters. generate_session_id This method will return a string that can be used as a session ID. It is supposed to be a reasonably random string with enough bits to prevent collision. It basically takes "session_hash_seed" and hashes it using SHA-1, MD5 or SHA-256, depending on the availability of these modules. session_hash_seed This method is actually rather internal to generate_session_id, but should be overridable in case you want to provide more random data. Currently it returns a concatenated string which contains: * A counter * The current time * One value from "rand". * The stringified value of a newly allocated hash reference * The stringified value of the Catalyst context object in the hopes that those combined values are entropic enough for most uses. If this is not the case you can replace "session_hash_seed" with e.g. sub session_hash_seed { open my $fh, "<", "/dev/random"; read $fh, my $bytes, 20; close $fh; return $bytes; } Or even more directly, replace "generate_session_id": sub generate_session_id { open my $fh, "<", "/dev/random"; read $fh, my $bytes, 20; close $fh; return unpack("H*", $bytes); } Also have a look at Crypt::Random and the various openssl bindings - these modules provide APIs for cryptographically secure random data. finalize_session Clean up the session during "finalize". This clears the various accessors after saving to the store. dump_these See "dump_these" in Catalyst - ammends the session data structure to the list of dumped objects if session ID is defined. calculate_extended_session_expires calculate_initial_session_expires create_session_id_if_needed delete_session_id extend_session_expires Note: this is *not* used to give an individual user a longer session. See 'change_session_expires'. extend_session_id get_session_id reset_session_expires session_is_valid set_session_id initial_session_expires USING SESSIONS DURING PREPARE The earliest point in time at which you may use the session data is after Catalyst::Plugin::Session's "prepare_action" has finished. State plugins must set $c->session ID before "prepare_action", and during "prepare_action" Catalyst::Plugin::Session will actually load the data from the store. sub prepare_action { my $c = shift; # don't touch $c->session yet! $c->NEXT::prepare_action( @_ ); $c->session; # this is OK $c->sessionid; # this is also OK } CONFIGURATION $c->config('Plugin::Session' => { expires => 1234, }); All configuation parameters are provided in a hash reference under the "Plugin::Session" key in the configuration hash. expires The time-to-live of each session, expressed in seconds. Defaults to 7200 (two hours). expiry_threshold Only update the session expiry time if it would otherwise expire within this many seconds from now. The purpose of this is to keep the session store from being updated when nothing else in the session is updated. Defaults to 0 (in which case, the expiration will always be updated). verify_address When true, "$c->request->address" will be checked at prepare time. If it is not the same as the address that initiated the session, the session is deleted. Defaults to false. verify_user_agent When true, "$c->request->user_agent" will be checked at prepare time. If it is not the same as the user agent that initiated the session, the session is deleted. Defaults to false. flash_to_stash This option makes it easier to have actions behave the same whether they were forwarded to or redirected to. On prepare time it copies the contents of "flash" (if any) to the stash. SPECIAL KEYS The hash reference returned by "$c->session" contains several keys which are automatically set: __expires This key no longer exists. Use "session_expires" instead. __updated The last time a session was saved to the store. __created The time when the session was first created. __address The value of "$c->request->address" at the time the session was created. This value is only populated if "verify_address" is true in the configuration. __user_agent The value of "$c->request->user_agent" at the time the session was created. This value is only populated if "verify_user_agent" is true in the configuration. CAVEATS Round the Robin Proxies "verify_address" could make your site inaccessible to users who are behind load balanced proxies. Some ISPs may give a different IP to each request by the same client due to this type of proxying. If addresses are verified these users' sessions cannot persist. To let these users access your site you can either disable address verification as a whole, or provide a checkbox in the login dialog that tells the server that it's OK for the address of the client to change. When the server sees that this box is checked it should delete the "__address" special key from the session hash when the hash is first created. Race Conditions In this day and age where cleaning detergents and Dutch football (not the American kind) teams roam the plains in great numbers, requests may happen simultaneously. This means that there is some risk of session data being overwritten, like this: 1. request a starts, request b starts, with the same session ID 2. session data is loaded in request a 3. session data is loaded in request b 4. session data is changed in request a 5. request a finishes, session data is updated and written to store 6. request b finishes, session data is updated and written to store, overwriting changes by request a For applications where any given user's session is only making one request at a time this plugin should be safe enough. AUTHORS Andy Grundman Christian Hansen Yuval Kogman, "nothingmuch@woobling.org" Sebastian Riedel Tomas Doran (t0m) "bobtfish@bobtfish.net" (current maintainer) Sergio Salvi kmx "kmx@volny.cz" Florian Ragwitz (rafl) "rafl@debian.org" Kent Fredric (kentnl) And countless other contributers from #catalyst. Thanks guys! Contributors Devin Austin (dhoss) Robert Rothenberg (on behalf of Foxtons Ltd.) COPYRIGHT & LICENSE Copyright (c) 2005 the aforementioned authors. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. Catalyst-Plugin-Session-0.43/Changes000644 000000 000000 00000021561 14246413476 017513 0ustar00rootwheel000000 000000 Revision history for Perl extension Catalyst::Plugin::Session 0.43 - 2022-06-03 - fix tests when Catalyst::Plugin::Authentication is unavailable 0.42 - 2022-05-31 - revised packaging - correctly specify test prerequisites as test prerequisites - drop unused Test::Exception prereq - drop Tie::RefHash prereq that was not used directly - only run pod tests for authors - ensure all optional tests are run by authors - drop use of Test::WWW::Mechanize::PSGI and Test::WWW::Mechanize::Catalyst in favor of a simpler user agent 0.41 2018-12-05 - Don't let an evil session ID supplier have an easy XSS vector (Michael McClimon++) 0.40 2015-01-26 - Add a flag so that a storage can finalize during finalize_header rather than finalize_body. This is to enable storages that need to write to the HTTP header (such as the cookie based store). 0.39 2013-10-16 - Fixed a bug when "expiry_threshold" is non-zero, where changes to the session were not saved. 0.38 2013-09-18 - New feature: "expiry_threshold" which allows you more control over when this plugin checks and updates the expiration date for the session. This is useful when you have high traffic and need to reduce the number of session expiration hits (like if you are using a database for sessions and your db is getting pounded). 0.37 2013-02-25 - Fix t/live_verify_address.t to skip if Catalyst::Plugin::Authentication is not installed, fixing RT#81506. 0.36 2012-10-19 - Re-pack with new Module::Install which doesn't get MYMETA.yaml wrong. - Remove use of Plack::Middleware::ForceEnv from the tests as it was not used / needed 0.35 2012-04-24 - Implement a 'change_session_expires' method (gshank) - Fixed bug from last version where session does not persist across a redirect 0.34 2012-03-30 - Fixed up t/live_verify_address.t per https://rt.cpan.org/Ticket/Display.html?id=71142 - Merged in dpetrov's 0.32 changes (extend_session_expire) 0.33 2012-03-08 - Note that flash is deprecated / not recommended due to it's inherent races. Point out Catalyst::Plugin::StatusMessage instead 0.32 2011-06-08 - Fix handling with enables verify_address and add related test 0.31 2010-10-08 - Fix session being loaded by call to dump_these in debug mode (RT#58856) 0.30 2010-06-24 - Fix Makefile.PL's is_upgrading_needed() routine (RT #58771) 0.29 2009-11-04 - Fix session being deleted when you have a new session after session expiry when calling session_is_valid method. Tests for this. - Allow ->session to be used as a setter method so that you can say ->session( key => $value ); 0.28 2009-10-29 - Fix session fixation test with LWP 5.833 by calling $cookie_jar->set_cookie rather than manually stuffing the cookie in the request. 0.27 2009-10-08 - Release 0.26_01 as stable without further changes. 0.26_01 2009-10-06 - Move actions out of the root application class in tests as this is deprecated. - Change configuration key to 'Plugin::Session' by default. The old 'session' key is still supported, but will issue a warning in a future release. 0.26 2009-08-19 - Remove Test::MockObject from the test suite as prone to failing on some platforms and perl versions due to it's UNIVERSAL:: package dependencies. 0.25 2009-07-08 - Add the a change_session_id method which can be called after authentication to change the user's session cookie whilst preserving their session data. This can be used to provide protection from Session Fixation attacks. (kmx) 0.24 2009-06-23 - Be more paranoid about getting values of $c->req to avoid issues with old Test::WWW::Mechanize::Catalyst. - Check we have a modern version of TWMC before doing the tests which need it. 0.23 2009-06-16 - Add the verify_user_agent config parameter (kmx) - Add a test case to prove that logging in with a session cookie still causes a new cookie to be issued for you, proving that the code is not vulnerable to a session fixation attack. (t0m) 0.22 2009-05-13 - INSANE HACK to ensure B::Hooks::EndOfScope inlines us a new method right now in Catalyst::Plugin::Session::Test::Store for Catalyst 5.80004 compatibility. This change does not in any way affect normal users - it is just due to the fairly crazy way that Catalyst::Plugin::Session::Test::Store works, and that module is _only_ used for unit testing session store plugins pre-installation. Session::Test::Store should be replaced with a more sane solution, and other CPAN modules using it moved away from using it, but this change keeps stops new Catalyst breaking other distributions right now. 0.21 2009-04-30 - Hide the internal packages in Catalyst::Plugin::Session::Test::Store from PAUSE. - Convert from CAF to Moose with Moosex::Emulate::Class::Accessor::Fast 0.20 2009-02-05 - No code changes since 0.19_01 dev release. - Add IDEAS.txt which is an irc log of discussion about the next-generation session plugin from discussion on #catalyst-dev - Remove TODO file, which is no longer relevant. 0.19_01 2009-01-09 - Switch from using NEXT to Class::C3 for method re-dispatch. - Use shipit to package the dist. - Switch to Module::install. - Flash data is now stored inside the session (key "__flash") to avoid duplicate entry errors caused by simultaneous select/insert/delete of flash rows when using DBI as a Store. (Sergio Salvi) - Fix session finalization order that caused HTTP responses to be sent before the session is actually finalized and stored in its Store. (Sergio Salvi) 0.19 2007-10-08 0.18 2007-08-15 - Fix Apache engine issue (RT #28845) 0.17 2007-07-16 - Skip a test if Cookie is not installed (RT #28137) 0.16 2007-07-03 - Stupid makefile 0.15 2007-06-24 - Fix the bug that caused sessions to expire immediately when another session was deleted previously in the same request cycle - Changed finalize() to redispatch before saving session so other finalize methods still have access to it. 0.14 2007-01-31 - Disable verify_address. - update flash to work like session 0.13 2006-10-12 - Rerelease with slightly changed test due to a behavior change in Test::MockObject - add `clear_flash` - improve debug logging 0.12 2006-08-26 - refactor out a hookable finalize_session method, for plugins - make _clear_session_instance_data call NEXT::, so that plugins can hook on to that too 0.11 2006-08-10 - Lazify expiry calculation and store it in a different instance data slot. This provides greater flexibility for implementing hooks like DynamicExpiry the "right" way. 0.10 2006-08-01 - Implement a more well defined finalization order for Session stuff. This solves a problem that was introduced by some value cleanups in the 0.06 release. 0.09 2006-07-31 - Fix Catalyst::Plugin::Session::Test::Store 0.08 2006-07-31 - rerelease because Module::Bane broke the META.yml. HURAAH 0.07 2006-07-30 - Make build tool complain loudly on incompatible versions of state plugins. 0.06 2006-07-29 - Change State plugin API to be pull oriented - Lazify more correctly (mostly performance improvements) - Don't try to compute digest of hash when there is no hash 0.05 2006-01-01 - Un-workaround the Cache::FastMmap (actually Storable) limitation - it's not C::P::Session's business. - add $c->session_expires - refactor guts - improve semantics of session deletion (now deletes flash data too) - improve lazy-load-ness of session data in the light of expiration 0.04 2005-12-28 09:42:00 - Work around a limitation in Cache::FastMmap - must store only references, while expiration was an NV. 0.03 2005-12-26 10:22:00 - Lazify loading of session data for better performance and less chance of race conditions - support for $c->flash a la Ruby on Rails - Fixed bug in sessionid algorithm detection. - Separate __expires from the session data - we write it every time - Lazify saving of session data for better performance and less chance of race conditions 0.02 2005-11-23 09:40:00 - Doc fixes - No more -Engine=Test 0.01 2005-11-14 12:41:00 - Initial release. Catalyst-Plugin-Session-0.43/MANIFEST000644 000000 000000 00000002172 14246413534 017341 0ustar00rootwheel000000 000000 Changes lib/Catalyst/Plugin/Session.pm lib/Catalyst/Plugin/Session/State.pm lib/Catalyst/Plugin/Session/Store.pm lib/Catalyst/Plugin/Session/Store/Dummy.pm lib/Catalyst/Plugin/Session/Test/Store.pm lib/Catalyst/Plugin/Session/Tutorial.pod maint/Makefile.PL.include Makefile.PL MANIFEST This list of files t/00_basic_sanity.t t/01_setup.t t/03_flash.t t/05_semi_persistent_flash.t t/cat_test.t t/lib/FlashTestApp.pm t/lib/FlashTestApp/Controller/Root.pm t/lib/MiniUA.pm t/lib/SessionExpiry.pm t/lib/SessionExpiry/Controller/Root.pm t/lib/SessionTestApp.pm t/lib/SessionTestApp/Controller/Root.pm t/lib/SessionValid.pm t/lib/SessionValid/Controller/Root.pm t/live_accessor.t t/live_app.t t/live_expiry_threshold.t t/live_session_fixation.t t/live_verify_address.t t/live_verify_user_agent.t t/session_valid.t xt/pod.t xt/podcoverage.t META.yml Module YAML meta-data (added by MakeMaker) META.json Module JSON meta-data (added by MakeMaker) README README file (added by Distar) LICENSE LICENSE file (added by Distar) Catalyst-Plugin-Session-0.43/LICENSE000644 000000 000000 00000043440 14246413534 017220 0ustar00rootwheel000000 000000 Terms of the Perl programming language system itself a) the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version, or b) the "Artistic License" --- The GNU General Public License, Version 1, February 1989 --- This software is Copyright (c) 2022 by Yuval Kogman . This is free software, licensed under: The GNU General Public License, Version 1, February 1989 GNU GENERAL PUBLIC LICENSE Version 1, February 1989 Copyright (C) 1989 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The license agreements of most software companies try to keep users at the mercy of those companies. By contrast, our General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. The General Public License applies to the Free Software Foundation's software and to any other program whose authors commit to using it. You can use it for your programs, too. When we speak of free software, we are referring to freedom, not price. Specifically, the General Public License is designed to make sure that you have the freedom to give away or sell copies of free software, that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of a such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must tell them their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any work containing the Program or a portion of it, either verbatim or with modifications. Each licensee is addressed as "you". 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this General Public License and to the absence of any warranty; and give any other recipients of the Program a copy of this General Public License along with the Program. You may charge a fee for the physical act of transferring a copy. 2. You may modify your copy or copies of the Program or any portion of it, and copy and distribute such modifications under the terms of Paragraph 1 above, provided that you also do the following: a) cause the modified files to carry prominent notices stating that you changed the files and the date of any change; and b) cause the whole of any work that you distribute or publish, that in whole or in part contains the Program or any part thereof, either with or without modifications, to be licensed at no charge to all third parties under the terms of this General Public License (except that you may choose to grant warranty protection to some or all third parties, at your option). c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the simplest and most usual way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this General Public License. d) You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. Mere aggregation of another independent work with the Program (or its derivative) on a volume of a storage or distribution medium does not bring the other work under the scope of these terms. 3. You may copy and distribute the Program (or a portion or derivative of it, under Paragraph 2) in object code or executable form under the terms of Paragraphs 1 and 2 above provided that you also do one of the following: a) accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Paragraphs 1 and 2 above; or, b) accompany it with a written offer, valid for at least three years, to give any third party free (except for a nominal charge for the cost of distribution) a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Paragraphs 1 and 2 above; or, c) accompany it with the information you received as to where the corresponding source code may be obtained. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form alone.) Source code for a work means the preferred form of the work for making modifications to it. For an executable file, complete source code means all the source code for all modules it contains; but, as a special exception, it need not include source code for modules which are standard libraries that accompany the operating system on which the executable file runs, or for standard header files or definitions files that accompany that operating system. 4. You may not copy, modify, sublicense, distribute or transfer the Program except as expressly provided under this General Public License. Any attempt otherwise to copy, modify, sublicense, distribute or transfer the Program is void, and will automatically terminate your rights to use the Program under this License. However, parties who have received copies, or rights to use copies, from you under this General Public License will not have their licenses terminated so long as such parties remain in full compliance. 5. By copying, distributing or modifying the Program (or any work based on the Program) you indicate your acceptance of this license to do so, and all its terms and conditions. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. 7. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of the license which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the license, you may choose any version ever published by the Free Software Foundation. 8. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS Appendix: How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to humanity, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy This program 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 1, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19xx name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (a program to direct compilers to make passes at assemblers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice That's all there is to it! --- The Artistic License 1.0 --- This software is Copyright (c) 2022 by Yuval Kogman . This is free software, licensed under: The Artistic License 1.0 The Artistic License Preamble The intent of this document is to state the conditions under which a Package may be copied, such that the Copyright Holder maintains some semblance of artistic control over the development of the package, while giving the users of the package the right to use and distribute the Package in a more-or-less customary fashion, plus the right to make reasonable modifications. Definitions: - "Package" refers to the collection of files distributed by the Copyright Holder, and derivatives of that collection of files created through textual modification. - "Standard Version" refers to such a Package if it has not been modified, or has been modified in accordance with the wishes of the Copyright Holder. - "Copyright Holder" is whoever is named in the copyright or copyrights for the package. - "You" is you, if you're thinking about copying or distributing this Package. - "Reasonable copying fee" is whatever you can justify on the basis of media cost, duplication charges, time of people involved, and so on. (You will not be required to justify it to the Copyright Holder, but only to the computing community at large as a market that must bear the fee.) - "Freely Available" means that no fee is charged for the item itself, though there may be fees involved in handling the item. It also means that recipients of the item may redistribute it under the same conditions they received it. 1. You may make and give away verbatim copies of the source form of the Standard Version of this Package without restriction, provided that you duplicate all of the original copyright notices and associated disclaimers. 2. You may apply bug fixes, portability fixes and other modifications derived from the Public Domain or from the Copyright Holder. A Package modified in such a way shall still be considered the Standard Version. 3. You may otherwise modify your copy of this Package in any way, provided that you insert a prominent notice in each changed file stating how and when you changed that file, and provided that you do at least ONE of the following: a) place your modifications in the Public Domain or otherwise make them Freely Available, such as by posting said modifications to Usenet or an equivalent medium, or placing the modifications on a major archive site such as ftp.uu.net, or by allowing the Copyright Holder to include your modifications in the Standard Version of the Package. b) use the modified Package only within your corporation or organization. c) rename any non-standard executables so the names do not conflict with standard executables, which must also be provided, and provide a separate manual page for each non-standard executable that clearly documents how it differs from the Standard Version. d) make other distribution arrangements with the Copyright Holder. 4. You may distribute the programs of this Package in object code or executable form, provided that you do at least ONE of the following: a) distribute a Standard Version of the executables and library files, together with instructions (in the manual page or equivalent) on where to get the Standard Version. b) accompany the distribution with the machine-readable source of the Package with your modifications. c) accompany any non-standard executables with their corresponding Standard Version executables, giving the non-standard executables non-standard names, and clearly documenting the differences in manual pages (or equivalent), together with instructions on where to get the Standard Version. d) make other distribution arrangements with the Copyright Holder. 5. You may charge a reasonable copying fee for any distribution of this Package. You may charge any fee you choose for support of this Package. You may not charge a fee for this Package itself. However, you may distribute this Package in aggregate with other (possibly commercial) programs as part of a larger (possibly commercial) software distribution provided that you do not advertise this Package as a product of your own. 6. The scripts and library files supplied as input to or produced as output from the programs of this Package do not automatically fall under the copyright of this Package, but belong to whomever generated them, and may be sold commercially, and may be aggregated with this Package. 7. C or perl subroutines supplied by you and linked into this Package shall not be considered part of this Package. 8. The name of the Copyright Holder may not be used to endorse or promote products derived from this software without specific prior written permission. 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. The End Catalyst-Plugin-Session-0.43/t/000755 000000 000000 00000000000 14246413532 016447 5ustar00rootwheel000000 000000 Catalyst-Plugin-Session-0.43/xt/000755 000000 000000 00000000000 14246413532 016637 5ustar00rootwheel000000 000000 Catalyst-Plugin-Session-0.43/META.yml000644 000000 000000 00000002420 14246413533 017454 0ustar00rootwheel000000 000000 --- abstract: 'Generic Session plugin - ties together server side storage and client side state required to maintain session data.' author: - 'Yuval Kogman ' build_requires: Plack::Test: '0' Test::Deep: '0' Test::Needs: '0' configure_requires: ExtUtils::MakeMaker: '0' dynamic_config: 1 generated_by: 'ExtUtils::MakeMaker version 7.64, CPAN::Meta::Converter version 2.150010' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: Catalyst-Plugin-Session no_index: directory: - t - xt requires: Catalyst::Runtime: '5.71001' Digest: '0' File::Spec: '0' File::Temp: '0' HTML::Entities: '0' List::Util: '0' MRO::Compat: '0' Moose: '0.76' MooseX::Emulate::Class::Accessor::Fast: '0.00801' Object::Signature: '0' Test::More: '0.88' namespace::clean: '0.10' perl: '5.008' resources: bugtracker: https://rt.cpan.org/Public/Dist/Display.html?Name=Catalyst-Plugin-Session license: http://dev.perl.org/licenses/ repository: https://github.com/perl-catalyst/Catalyst-Plugin-Session.git version: '0.43' x_breaks: Catalyst::Plugin::Session::State::Cookie: '< 0.03' Catalyst::Plugin::Session::State::URI: '< 0.02' x_serialization_backend: 'CPAN::Meta::YAML version 0.018' Catalyst-Plugin-Session-0.43/META.json000644 000000 000000 00000004432 14246413534 017632 0ustar00rootwheel000000 000000 { "abstract" : "Generic Session plugin - ties together server side storage and client side state required to maintain session data.", "author" : [ "Yuval Kogman " ], "dynamic_config" : 1, "generated_by" : "ExtUtils::MakeMaker version 7.64, CPAN::Meta::Converter version 2.150010", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : 2 }, "name" : "Catalyst-Plugin-Session", "no_index" : { "directory" : [ "t", "xt" ] }, "prereqs" : { "build" : {}, "configure" : { "requires" : { "ExtUtils::MakeMaker" : "0" } }, "develop" : { "requires" : { "Test::Pod" : "0", "Test::Pod::Coverage" : "0" } }, "runtime" : { "requires" : { "Catalyst::Runtime" : "5.71001", "Digest" : "0", "File::Spec" : "0", "File::Temp" : "0", "HTML::Entities" : "0", "List::Util" : "0", "MRO::Compat" : "0", "Moose" : "0.76", "MooseX::Emulate::Class::Accessor::Fast" : "0.00801", "Object::Signature" : "0", "Test::More" : "0.88", "namespace::clean" : "0.10", "perl" : "5.008" } }, "test" : { "requires" : { "Plack::Test" : "0", "Test::Deep" : "0", "Test::Needs" : "0" } } }, "release_status" : "stable", "resources" : { "bugtracker" : { "mailto" : "bug-Catalyst-Plugin-Session@rt.cpan.org", "web" : "https://rt.cpan.org/Public/Dist/Display.html?Name=Catalyst-Plugin-Session" }, "license" : [ "http://dev.perl.org/licenses/" ], "repository" : { "type" : "git", "url" : "https://github.com/perl-catalyst/Catalyst-Plugin-Session.git", "web" : "https://github.com/perl-catalyst/Catalyst-Plugin-Session" } }, "version" : "0.43", "x_breaks" : { "Catalyst::Plugin::Session::State::Cookie" : "< 0.03", "Catalyst::Plugin::Session::State::URI" : "< 0.02" }, "x_serialization_backend" : "JSON::PP version 4.07" } Catalyst-Plugin-Session-0.43/lib/000755 000000 000000 00000000000 14246413532 016752 5ustar00rootwheel000000 000000 Catalyst-Plugin-Session-0.43/maint/000755 000000 000000 00000000000 14246413532 017314 5ustar00rootwheel000000 000000 Catalyst-Plugin-Session-0.43/Makefile.PL000644 000000 000000 00000007420 14245255735 020171 0ustar00rootwheel000000 000000 use strict; use warnings; use 5.008; my %META = ( name => 'Catalyst-Plugin-Session', license => 'perl_5', prereqs => { configure => { requires => { 'ExtUtils::MakeMaker' => 0, } }, test => { requires => { 'Plack::Test' => 0, 'Test::Deep' => 0, 'Test::Needs' => 0, }, }, runtime => { requires => { 'Catalyst::Runtime' => '5.71001', 'namespace::clean' => '0.10', 'Digest' => 0, 'File::Spec' => 0, 'File::Temp' => 0, 'List::Util' => 0, 'Object::Signature' => 0, 'MRO::Compat' => 0, 'MooseX::Emulate::Class::Accessor::Fast' => '0.00801', 'Moose' => '0.76', 'HTML::Entities' => 0, 'Test::More' => '0.88', 'perl' => '5.008', }, }, develop => { requires => { 'Test::Pod' => 0, 'Test::Pod::Coverage' => 0, }, }, }, x_breaks => { 'Catalyst::Plugin::Session::State::Cookie' => '< 0.03', 'Catalyst::Plugin::Session::State::URI' => '< 0.02', }, resources => { repository => { url => 'https://github.com/perl-catalyst/Catalyst-Plugin-Session.git', web => 'https://github.com/perl-catalyst/Catalyst-Plugin-Session', type => 'git', }, bugtracker => { web => 'https://rt.cpan.org/Public/Dist/Display.html?Name=Catalyst-Plugin-Session', mailto => 'bug-Catalyst-Plugin-Session@rt.cpan.org', }, license => [ 'http://dev.perl.org/licenses/' ], }, no_index => { directory => [ 't', 'xt' ] }, ); my %MM_ARGS = (); for my $breaks (sort keys %{ $META{x_breaks} }) { my $version = $META{x_breaks}{$breaks}; $version =~ s{\A<\s+([0-9.]+)\z}{$1} or die "can't handle $version"; if (eval "require $breaks") { if (!eval { $breaks->VERSION($version) }) { warn <= 6.57_02; my $mymeta_broken = $mymeta && $eumm_version < 6.57_07; ($MM_ARGS{NAME} = $META{name}) =~ s/-/::/g; ($MM_ARGS{VERSION_FROM} = "lib/$MM_ARGS{NAME}.pm") =~ s{::}{/}g; $META{license} = [ $META{license} ] if $META{license} && !ref $META{license}; $MM_ARGS{LICENSE} = $META{license}[0] if $META{license} && $eumm_version >= 6.30; $MM_ARGS{NO_MYMETA} = 1 if $mymeta_broken; $MM_ARGS{META_ADD} = { 'meta-spec' => { version => 2 }, %META } unless -f 'META.yml'; $MM_ARGS{PL_FILES} ||= {}; $MM_ARGS{NORECURS} = 1 if not exists $MM_ARGS{NORECURS}; for (qw(configure build test runtime)) { my $key = $_ eq 'runtime' ? 'PREREQ_PM' : uc $_.'_REQUIRES'; my $r = $MM_ARGS{$key} = { %{$META{prereqs}{$_}{requires} || {}}, %{delete $MM_ARGS{$key} || {}}, }; defined $r->{$_} or delete $r->{$_} for keys %$r; } $MM_ARGS{MIN_PERL_VERSION} = delete $MM_ARGS{PREREQ_PM}{perl} || 0; delete $MM_ARGS{MIN_PERL_VERSION} if $eumm_version < 6.47_01; $MM_ARGS{BUILD_REQUIRES} = {%{$MM_ARGS{BUILD_REQUIRES}}, %{delete $MM_ARGS{TEST_REQUIRES}}} if $eumm_version < 6.63_03; $MM_ARGS{PREREQ_PM} = {%{$MM_ARGS{PREREQ_PM}}, %{delete $MM_ARGS{BUILD_REQUIRES}}} if $eumm_version < 6.55_01; delete $MM_ARGS{CONFIGURE_REQUIRES} if $eumm_version < 6.51_03; ExtUtils::MakeMaker::WriteMakefile(%MM_ARGS); ## END BOILERPLATE ########################################################### Catalyst-Plugin-Session-0.43/maint/Makefile.PL.include000644 000000 000000 00000000263 14245254614 022714 0ustar00rootwheel000000 000000 BEGIN { -e 'Distar' or system qw(git clone https://github.com/p5sagit/Distar.git) } use lib 'Distar/lib'; use Distar 0.001; author 'Yuval Kogman '; 1; Catalyst-Plugin-Session-0.43/lib/Catalyst/000755 000000 000000 00000000000 14246413532 020536 5ustar00rootwheel000000 000000 Catalyst-Plugin-Session-0.43/lib/Catalyst/Plugin/000755 000000 000000 00000000000 14246413532 021774 5ustar00rootwheel000000 000000 Catalyst-Plugin-Session-0.43/lib/Catalyst/Plugin/Session.pm000644 000000 000000 00000074665 14246413470 024000 0ustar00rootwheel000000 000000 package Catalyst::Plugin::Session; use Moose; with 'MooseX::Emulate::Class::Accessor::Fast'; use MRO::Compat; use Catalyst::Exception (); use Digest (); use overload (); use Object::Signature (); use HTML::Entities (); use Carp; use List::Util qw/ max /; use namespace::clean -except => 'meta'; our $VERSION = '0.43'; $VERSION =~ tr/_//d; my @session_data_accessors; # used in delete_session __PACKAGE__->mk_accessors( "_session_delete_reason", @session_data_accessors = qw/ _sessionid _session _session_expires _extended_session_expires _session_data_sig _flash _flash_keep_keys _flash_key_hashes _tried_loading_session_id _tried_loading_session_data _tried_loading_session_expires _tried_loading_flash_data _needs_early_session_finalization / ); sub _session_plugin_config { my $c = shift; # FIXME - Start warning once all the state/store modules have also been updated. #$c->log->warn("Deprecated 'session' config key used, please use the key 'Plugin::Session' instead") # if exists $c->config->{session} #$c->config->{'Plugin::Session'} ||= delete($c->config->{session}) || {}; $c->config->{'Plugin::Session'} ||= $c->config->{session} || {}; } sub setup { my $c = shift; $c->maybe::next::method(@_); $c->check_session_plugin_requirements; $c->setup_session; return $c; } sub check_session_plugin_requirements { my $c = shift; unless ( $c->isa("Catalyst::Plugin::Session::State") && $c->isa("Catalyst::Plugin::Session::Store") ) { my $err = ( "The Session plugin requires both Session::State " . "and Session::Store plugins to be used as well." ); $c->log->fatal($err); Catalyst::Exception->throw($err); } } sub setup_session { my $c = shift; my $cfg = $c->_session_plugin_config; %$cfg = ( expires => 7200, verify_address => 0, verify_user_agent => 0, expiry_threshold => 0, %$cfg, ); $c->maybe::next::method(); } sub prepare_action { my $c = shift; $c->maybe::next::method(@_); if ( $c->_session_plugin_config->{flash_to_stash} and $c->sessionid and my $flash_data = $c->flash ) { @{ $c->stash }{ keys %$flash_data } = values %$flash_data; } } sub finalize_headers { my $c = shift; # fix cookie before we send headers $c->_save_session_expires; # Force extension of session_expires before finalizing headers, so a pos # up to date. First call to session_expires will extend the expiry, subs # just return the previously extended value. $c->session_expires; $c->finalize_session if $c->_needs_early_session_finalization; return $c->maybe::next::method(@_); } sub finalize_body { my $c = shift; # We have to finalize our session *before* $c->engine->finalize_xxx is called, # because we do not want to send the HTTP response before the session is stored/committed to # the session database (or whatever Session::Store you use). $c->finalize_session unless $c->_needs_early_session_finalization; $c->_clear_session_instance_data; return $c->maybe::next::method(@_); } sub finalize_session { my $c = shift; $c->maybe::next::method(@_); $c->_save_session_id; $c->_save_session; $c->_save_flash; } sub _session_updated { my $c = shift; if ( my $session_data = $c->_session ) { no warnings 'uninitialized'; if ( Object::Signature::signature($session_data) ne $c->_session_data_sig ) { return $session_data; } else { return; } } else { return; } } sub _save_session_id { my $c = shift; # we already called set when allocating # no need to tell the state plugins anything new } sub _save_session_expires { my $c = shift; if ( defined($c->_session_expires) ) { if (my $sid = $c->sessionid) { my $current = $c->_get_stored_session_expires; my $extended = $c->session_expires; if ($extended > $current) { $c->store_session_data( "expires:$sid" => $extended ); } } } } sub _save_session { my $c = shift; if ( my $session_data = $c->_session_updated ) { $session_data->{__updated} = time(); my $sid = $c->sessionid; $c->store_session_data( "session:$sid" => $session_data ); } } sub _save_flash { my $c = shift; if ( my $flash_data = $c->_flash ) { my $hashes = $c->_flash_key_hashes || {}; my $keep = $c->_flash_keep_keys || {}; foreach my $key ( keys %$hashes ) { if ( !exists $keep->{$key} and Object::Signature::signature( \$flash_data->{$key} ) eq $hashes->{$key} ) { delete $flash_data->{$key}; } } my $sid = $c->sessionid; my $session_data = $c->_session; if (%$flash_data) { $session_data->{__flash} = $flash_data; } else { delete $session_data->{__flash}; } $c->_session($session_data); $c->_save_session; } } sub _load_session_expires { my $c = shift; return $c->_session_expires if $c->_tried_loading_session_expires; $c->_tried_loading_session_expires(1); if ( my $sid = $c->sessionid ) { my $expires = $c->_get_stored_session_expires; if ( $expires >= time() ) { $c->_session_expires( $expires ); return $expires; } else { $c->delete_session( "session expired" ); return 0; } } return; } sub _load_session { my $c = shift; return $c->_session if $c->_tried_loading_session_data; $c->_tried_loading_session_data(1); if ( my $sid = $c->sessionid ) { if ( $c->_load_session_expires ) { # > 0 my $session_data = $c->get_session_data("session:$sid") || return; $c->_session($session_data); no warnings 'uninitialized'; # ne __address if ( $c->_session_plugin_config->{verify_address} && exists $session_data->{__address} && $session_data->{__address} ne $c->request->address ) { $c->log->warn( "Deleting session $sid due to address mismatch (" . $session_data->{__address} . " != " . $c->request->address . ")" ); $c->delete_session("address mismatch"); return; } if ( $c->_session_plugin_config->{verify_user_agent} && $session_data->{__user_agent} ne $c->request->user_agent ) { $c->log->warn( "Deleting session $sid due to user agent mismatch (" . $session_data->{__user_agent} . " != " . $c->request->user_agent . ")" ); $c->delete_session("user agent mismatch"); return; } $c->log->debug(qq/Restored session "$sid"/) if $c->debug; $c->_session_data_sig( Object::Signature::signature($session_data) ) if $session_data; $c->_expire_session_keys; return $session_data; } } return; } sub _load_flash { my $c = shift; return $c->_flash if $c->_tried_loading_flash_data; $c->_tried_loading_flash_data(1); if ( my $sid = $c->sessionid ) { my $session_data = $c->session; $c->_flash($session_data->{__flash}); if ( my $flash_data = $c->_flash ) { $c->_flash_key_hashes({ map { $_ => Object::Signature::signature( \$flash_data->{$_} ) } keys %$flash_data }); return $flash_data; } } return; } sub _expire_session_keys { my ( $c, $data ) = @_; my $now = time; my $expire_times = ( $data || $c->_session || {} )->{__expire_keys} || {}; foreach my $key ( grep { $expire_times->{$_} < $now } keys %$expire_times ) { delete $c->_session->{$key}; delete $expire_times->{$key}; } } sub _clear_session_instance_data { my $c = shift; $c->$_(undef) for @session_data_accessors; $c->maybe::next::method(@_); # allow other plugins to hook in on this } sub change_session_id { my $c = shift; my $sessiondata = $c->session; my $oldsid = $c->sessionid; my $newsid = $c->create_session_id; if ($oldsid) { $c->log->debug(qq/change_sessid: deleting session data from "$oldsid"/) if $c->debug; $c->delete_session_data("${_}:${oldsid}") for qw/session expires flash/; } $c->log->debug(qq/change_sessid: storing session data to "$newsid"/) if $c->debug; $c->store_session_data( "session:$newsid" => $sessiondata ); return $newsid; } sub delete_session { my ( $c, $msg ) = @_; $c->log->debug("Deleting session" . ( defined($msg) ? "($msg)" : '(no reason given)') ) if $c->debug; # delete the session data if ( my $sid = $c->sessionid ) { $c->delete_session_data("${_}:${sid}") for qw/session expires flash/; $c->delete_session_id($sid); } # reset the values in the context object # see the BEGIN block $c->_clear_session_instance_data; $c->_session_delete_reason($msg); } sub session_delete_reason { my $c = shift; $c->session_is_valid; # check that it was loaded $c->_session_delete_reason(@_); } sub session_expires { my $c = shift; if ( defined( my $expires = $c->_extended_session_expires ) ) { return $expires; } elsif ( defined( $expires = $c->_load_session_expires ) ) { return $c->extend_session_expires( $expires ); } else { return 0; } } sub extend_session_expires { my ( $c, $expires ) = @_; my $threshold = $c->_session_plugin_config->{expiry_threshold} || 0; if ( my $sid = $c->sessionid ) { my $expires = $c->_get_stored_session_expires; my $cutoff = $expires - $threshold; if (!$threshold || $cutoff <= time || $c->_session_updated) { $c->_extended_session_expires( my $updated = $c->calculate_initial_session_expires() ); $c->extend_session_id( $sid, $updated ); return $updated; } else { return $expires; } } else { return; } } sub change_session_expires { my ( $c, $expires ) = @_; $expires ||= 0; my $sid = $c->sessionid; my $time_exp = time() + $expires; $c->store_session_data( "expires:$sid" => $time_exp ); } sub _get_stored_session_expires { my ($c) = @_; if ( my $sid = $c->sessionid ) { return $c->get_session_data("expires:$sid") || 0; } else { return 0; } } sub initial_session_expires { my $c = shift; return ( time() + $c->_session_plugin_config->{expires} ); } sub calculate_initial_session_expires { my ($c) = @_; return max( $c->initial_session_expires, $c->_get_stored_session_expires ); } sub calculate_extended_session_expires { my ( $c, $prev ) = @_; return ( time() + $prev ); } sub reset_session_expires { my ( $c, $sid ) = @_; my $exp = $c->calculate_initial_session_expires; $c->_session_expires( $exp ); # # since we're setting _session_expires directly, make load_session_expires # actually use that value. # $c->_tried_loading_session_expires(1); $c->_extended_session_expires( $exp ); $exp; } sub sessionid { my $c = shift; return $c->_sessionid || $c->_load_sessionid; } sub _load_sessionid { my $c = shift; return if $c->_tried_loading_session_id; $c->_tried_loading_session_id(1); if ( defined( my $sid = $c->get_session_id ) ) { if ( $c->validate_session_id($sid) ) { # temporarily set the inner key, so that validation will work $c->_sessionid($sid); return $sid; } else { $sid = HTML::Entities::encode_entities($sid); my $err = "Tried to set invalid session ID '$sid'"; $c->log->error($err); Catalyst::Exception->throw($err); } } return; } sub session_is_valid { my $c = shift; # force a check for expiry, but also __address, etc if ( $c->_load_session ) { return 1; } else { return; } } sub validate_session_id { my ( $c, $sid ) = @_; $sid and $sid =~ /^[a-f\d]+$/i; } sub session { my $c = shift; my $session = $c->_session || $c->_load_session || do { $c->create_session_id_if_needed; $c->initialize_session_data; }; if (@_) { my $new_values = @_ > 1 ? { @_ } : $_[0]; croak('session takes a hash or hashref') unless ref $new_values; for my $key (keys %$new_values) { $session->{$key} = $new_values->{$key}; } } $session; } sub keep_flash { my ( $c, @keys ) = @_; my $href = $c->_flash_keep_keys || $c->_flash_keep_keys({}); (@{$href}{@keys}) = ((undef) x @keys); } sub _flash_data { my $c = shift; $c->_flash || $c->_load_flash || do { $c->create_session_id_if_needed; $c->_flash( {} ); }; } sub _set_flash { my $c = shift; if (@_) { my $items = @_ > 1 ? {@_} : $_[0]; croak('flash takes a hash or hashref') unless ref $items; @{ $c->_flash }{ keys %$items } = values %$items; } } sub flash { my $c = shift; $c->_flash_data; $c->_set_flash(@_); return $c->_flash; } sub clear_flash { my $c = shift; #$c->delete_session_data("flash:" . $c->sessionid); # should this be in here? or delayed till finalization? $c->_flash_key_hashes({}); $c->_flash_keep_keys({}); $c->_flash({}); } sub session_expire_key { my ( $c, %keys ) = @_; my $now = time; @{ $c->session->{__expire_keys} }{ keys %keys } = map { $now + $_ } values %keys; } sub initialize_session_data { my $c = shift; my $now = time; return $c->_session( { __created => $now, __updated => $now, ( $c->_session_plugin_config->{verify_address} ? ( __address => $c->request->address||'' ) : () ), ( $c->_session_plugin_config->{verify_user_agent} ? ( __user_agent => $c->request->user_agent||'' ) : () ), } ); } sub generate_session_id { my $c = shift; my $digest = $c->_find_digest(); $digest->add( $c->session_hash_seed() ); return $digest->hexdigest; } sub create_session_id_if_needed { my $c = shift; $c->create_session_id unless $c->sessionid; } sub create_session_id { my $c = shift; my $sid = $c->generate_session_id; $c->log->debug(qq/Created session "$sid"/) if $c->debug; $c->_sessionid($sid); $c->reset_session_expires; $c->set_session_id($sid); return $sid; } my $counter; sub session_hash_seed { my $c = shift; return join( "", ++$counter, time, rand, $$, {}, overload::StrVal($c), ); } my $usable; sub _find_digest () { unless ($usable) { foreach my $alg (qw/SHA-1 SHA-256 MD5/) { if ( eval { Digest->new($alg) } ) { $usable = $alg; last; } } Catalyst::Exception->throw( "Could not find a suitable Digest module. Please install " . "Digest::SHA1, Digest::SHA, or Digest::MD5" ) unless $usable; } return Digest->new($usable); } sub dump_these { my $c = shift; ( $c->maybe::next::method(), $c->_sessionid ? ( [ "Session ID" => $c->sessionid ], [ Session => $c->session ], ) : () ); } sub get_session_id { shift->maybe::next::method(@_) } sub set_session_id { shift->maybe::next::method(@_) } sub delete_session_id { shift->maybe::next::method(@_) } sub extend_session_id { shift->maybe::next::method(@_) } __PACKAGE__->meta->make_immutable; __END__ =pod =head1 NAME Catalyst::Plugin::Session - Generic Session plugin - ties together server side storage and client side state required to maintain session data. =head1 SYNOPSIS # To get sessions to "just work", all you need to do is use these plugins: use Catalyst qw/ Session Session::Store::FastMmap Session::State::Cookie /; # you can replace Store::FastMmap with Store::File - both have sensible # default configurations (see their docs for details) # more complicated backends are available for other scenarios (DBI storage, # etc) # after you've loaded the plugins you can save session data # For example, if you are writing a shopping cart, it could be implemented # like this: sub add_item : Local { my ( $self, $c ) = @_; my $item_id = $c->req->param("item"); # $c->session is a hash ref, a bit like $c->stash # the difference is that it' preserved across requests push @{ $c->session->{items} }, $item_id; $c->forward("MyView"); } sub display_items : Local { my ( $self, $c ) = @_; # values in $c->session are restored $c->stash->{items_to_display} = [ map { MyModel->retrieve($_) } @{ $c->session->{items} } ]; $c->forward("MyView"); } =head1 DESCRIPTION The Session plugin is the base of two related parts of functionality required for session management in web applications. The first part, the State, is getting the browser to repeat back a session key, so that the web application can identify the client and logically string several requests together into a session. The second part, the Store, deals with the actual storage of information about the client. This data is stored so that the it may be revived for every request made by the same client. This plugin links the two pieces together. =head1 RECOMENDED BACKENDS =over 4 =item Session::State::Cookie The only really sane way to do state is using cookies. =item Session::Store::File A portable backend, based on Cache::File. =item Session::Store::FastMmap A fast and flexible backend, based on Cache::FastMmap. =back =head1 METHODS =over 4 =item sessionid An accessor for the session ID value. =item session Returns a hash reference that might contain unserialized values from previous requests in the same session, and whose modified value will be saved for future requests. This method will automatically create a new session and session ID if none exists. You can also set session keys by passing a list of key/value pairs or a hashref. $c->session->{foo} = "bar"; # This works. $c->session(one => 1, two => 2); # And this. $c->session({ answer => 42 }); # And this. =item session_expires This method returns the time when the current session will expire, or 0 if there is no current session. If there is a session and it already expired, it will delete the session and return 0 as well. =item flash This is like Ruby on Rails' flash data structure. Think of it as a stash that lasts for longer than one request, letting you redirect instead of forward. The flash data will be cleaned up only on requests on which actually use $c->flash (thus allowing multiple redirections), and the policy is to delete all the keys which haven't changed since the flash data was loaded at the end of every request. Note that use of the flash is an easy way to get data across requests, but it's also strongly disrecommended, due it it being inherently plagued with race conditions. This means that it's unlikely to work well if your users have multiple tabs open at once, or if your site does a lot of AJAX requests. L is the recommended alternative solution, as this doesn't suffer from these issues. sub moose : Local { my ( $self, $c ) = @_; $c->flash->{beans} = 10; $c->response->redirect( $c->uri_for("foo") ); } sub foo : Local { my ( $self, $c ) = @_; my $value = $c->flash->{beans}; # ... $c->response->redirect( $c->uri_for("bar") ); } sub bar : Local { my ( $self, $c ) = @_; if ( exists $c->flash->{beans} ) { # false } } =item clear_flash Zap all the keys in the flash regardless of their current state. =item keep_flash @keys If you want to keep a flash key for the next request too, even if it hasn't changed, call C and pass in the keys as arguments. =item delete_session REASON This method is used to invalidate a session. It takes an optional parameter which will be saved in C if provided. NOTE: This method will B delete your flash data. =item session_delete_reason This accessor contains a string with the reason a session was deleted. Possible values include: =over 4 =item * C
=item * C =back =item session_expire_key $key, $ttl Mark a key to expire at a certain time (only useful when shorter than the expiry time for the whole session). For example: __PACKAGE__->config('Plugin::Session' => { expires => 10000000000 }); # "forever" (NB If this number is too large, Y2K38 breakage could result.) # later $c->session_expire_key( __user => 3600 ); Will make the session data survive, but the user will still be logged out after an hour. Note that these values are not auto extended. =item change_session_id By calling this method you can force a session id change while keeping all session data. This method might come handy when you are paranoid about some advanced variations of session fixation attack. If you want to prevent this session fixation scenario: 0) let us have WebApp with anonymous and authenticated parts 1) a hacker goes to vulnerable WebApp and gets a real sessionid, just by browsing anonymous part of WebApp 2) the hacker inserts (somehow) this values into a cookie in victim's browser 3) after the victim logs into WebApp the hacker can enter his/her session you should call change_session_id in your login controller like this: if ($c->authenticate( { username => $user, password => $pass } )) { # login OK $c->change_session_id; ... } else { # login FAILED ... } =item change_session_expires $expires You can change the session expiration time for this session; $c->change_session_expires( 4000 ); Note that this only works to set the session longer than the config setting. =back =head1 INTERNAL METHODS =over 4 =item setup This method is extended to also make calls to C and C. =item check_session_plugin_requirements This method ensures that a State and a Store plugin are also in use by the application. =item setup_session This method populates C<< $c->config('Plugin::Session') >> with the default values listed in L. =item prepare_action This method is extended. Its only effect is if the (off by default) C configuration parameter is on - then it will copy the contents of the flash to the stash at prepare time. =item finalize_headers This method is extended and will extend the expiry time before sending the response. =item finalize_body This method is extended and will call finalize_session before the other finalize_body methods run. Here we persist the session data if a session exists. =item initialize_session_data This method will initialize the internal structure of the session, and is called by the C method if appropriate. =item create_session_id Creates a new session ID using C if there is no session ID yet. =item validate_session_id SID Make sure a session ID is of the right format. This currently ensures that the session ID string is any amount of case insensitive hexadecimal characters. =item generate_session_id This method will return a string that can be used as a session ID. It is supposed to be a reasonably random string with enough bits to prevent collision. It basically takes C and hashes it using SHA-1, MD5 or SHA-256, depending on the availability of these modules. =item session_hash_seed This method is actually rather internal to generate_session_id, but should be overridable in case you want to provide more random data. Currently it returns a concatenated string which contains: =over 4 =item * A counter =item * The current time =item * One value from C. =item * The stringified value of a newly allocated hash reference =item * The stringified value of the Catalyst context object =back in the hopes that those combined values are entropic enough for most uses. If this is not the case you can replace C with e.g. sub session_hash_seed { open my $fh, "<", "/dev/random"; read $fh, my $bytes, 20; close $fh; return $bytes; } Or even more directly, replace C: sub generate_session_id { open my $fh, "<", "/dev/random"; read $fh, my $bytes, 20; close $fh; return unpack("H*", $bytes); } Also have a look at L and the various openssl bindings - these modules provide APIs for cryptographically secure random data. =item finalize_session Clean up the session during C. This clears the various accessors after saving to the store. =item dump_these See L - ammends the session data structure to the list of dumped objects if session ID is defined. =item calculate_extended_session_expires =item calculate_initial_session_expires =item create_session_id_if_needed =item delete_session_id =item extend_session_expires Note: this is *not* used to give an individual user a longer session. See 'change_session_expires'. =item extend_session_id =item get_session_id =item reset_session_expires =item session_is_valid =item set_session_id =item initial_session_expires =back =head1 USING SESSIONS DURING PREPARE The earliest point in time at which you may use the session data is after L's C has finished. State plugins must set $c->session ID before C, and during C L will actually load the data from the store. sub prepare_action { my $c = shift; # don't touch $c->session yet! $c->NEXT::prepare_action( @_ ); $c->session; # this is OK $c->sessionid; # this is also OK } =head1 CONFIGURATION $c->config('Plugin::Session' => { expires => 1234, }); All configuation parameters are provided in a hash reference under the C key in the configuration hash. =over 4 =item expires The time-to-live of each session, expressed in seconds. Defaults to 7200 (two hours). =item expiry_threshold Only update the session expiry time if it would otherwise expire within this many seconds from now. The purpose of this is to keep the session store from being updated when nothing else in the session is updated. Defaults to 0 (in which case, the expiration will always be updated). =item verify_address When true, C<< $c->request->address >> will be checked at prepare time. If it is not the same as the address that initiated the session, the session is deleted. Defaults to false. =item verify_user_agent When true, C<< $c->request->user_agent >> will be checked at prepare time. If it is not the same as the user agent that initiated the session, the session is deleted. Defaults to false. =item flash_to_stash This option makes it easier to have actions behave the same whether they were forwarded to or redirected to. On prepare time it copies the contents of C (if any) to the stash. =back =head1 SPECIAL KEYS The hash reference returned by C<< $c->session >> contains several keys which are automatically set: =over 4 =item __expires This key no longer exists. Use C instead. =item __updated The last time a session was saved to the store. =item __created The time when the session was first created. =item __address The value of C<< $c->request->address >> at the time the session was created. This value is only populated if C is true in the configuration. =item __user_agent The value of C<< $c->request->user_agent >> at the time the session was created. This value is only populated if C is true in the configuration. =back =head1 CAVEATS =head2 Round the Robin Proxies C could make your site inaccessible to users who are behind load balanced proxies. Some ISPs may give a different IP to each request by the same client due to this type of proxying. If addresses are verified these users' sessions cannot persist. To let these users access your site you can either disable address verification as a whole, or provide a checkbox in the login dialog that tells the server that it's OK for the address of the client to change. When the server sees that this box is checked it should delete the C<__address> special key from the session hash when the hash is first created. =head2 Race Conditions In this day and age where cleaning detergents and Dutch football (not the American kind) teams roam the plains in great numbers, requests may happen simultaneously. This means that there is some risk of session data being overwritten, like this: =over 4 =item 1. request a starts, request b starts, with the same session ID =item 2. session data is loaded in request a =item 3. session data is loaded in request b =item 4. session data is changed in request a =item 5. request a finishes, session data is updated and written to store =item 6. request b finishes, session data is updated and written to store, overwriting changes by request a =back For applications where any given user's session is only making one request at a time this plugin should be safe enough. =head1 AUTHORS Andy Grundman Christian Hansen Yuval Kogman, C Sebastian Riedel Tomas Doran (t0m) C (current maintainer) Sergio Salvi kmx C Florian Ragwitz (rafl) C Kent Fredric (kentnl) And countless other contributers from #catalyst. Thanks guys! =head1 Contributors Devin Austin (dhoss) Robert Rothenberg (on behalf of Foxtons Ltd.) =head1 COPYRIGHT & LICENSE Copyright (c) 2005 the aforementioned authors. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut Catalyst-Plugin-Session-0.43/lib/Catalyst/Plugin/Session/000755 000000 000000 00000000000 14246413532 023417 5ustar00rootwheel000000 000000 Catalyst-Plugin-Session-0.43/lib/Catalyst/Plugin/Session/Test/000755 000000 000000 00000000000 14246413532 024336 5ustar00rootwheel000000 000000 Catalyst-Plugin-Session-0.43/lib/Catalyst/Plugin/Session/Tutorial.pod000644 000000 000000 00000025105 13673241434 025734 0ustar00rootwheel000000 000000 =pod =head1 NAME Catalyst::Plugin::Session::Tutorial - Understanding and using sessions. =head1 ASSUMPTIONS This tutorial assumes that you are familiar with web applications in general and Catalyst specifically (up to models and configuration), and that you know what HTTP is. =head1 WHAT ARE SESSIONS When users use a site, especially one that knows who they are (sites you log in to, sites which let you keep a shopping cart, etc.), the server preparing the content has to know that request X comes from client A while request Y comes from client B, so that each user gets the content meant for them. The problem is that HTTP is a stateless protocol. This means that every request is distinct, and even if it comes from the same client, it's difficult to know that. The way sessions are maintained between distinct requests is that the client says, for every request, "I'm client A" or "I'm client B". This piece of data that tells the server "I'm X" is called the session ID, and the threading of several requests together is called a session. =head1 HOW SESSIONS WORK =head2 Cookies HTTP has a feature that lets this become easier, called cookies. A cookie is something the server asks the client to save somewhere, and resend every time a request is made. The way they work is that the server sends the C header, with a cookie name, a value, and some metadata (like when it expires, what paths it applies to, etc.). The client saves this. Then, on every subsequent request the client will send a C header, with the cookie name and value. =head2 Cookie Alternatives Another way is to make sure that the session ID is repeated is to include it in every URI. This can be as either a part of the path, or as a query parameter. This technique has several issues which are discussed in L. =head2 Server-Side Behavior When the server receives the session ID it can then look this key up in a database of some sort. For example the database can contain a shopping cart's contents, user preferences, etc. =head1 USING SESSIONS In L, the L plugin provides an API for convenient handling of session data. This API is based on the older, less flexible and less reliable L. The plugin is modular, and requires backend plugins to be used. =head2 State Plugins State plugins handle session ID persistence. For example L creates a cookie with the session ID in it. These plugins will automatically set C<< $c->sessionid >> at the beginning of the request, and automatically cause C<< $c->sessionid >> to be saved by the client at the end of the request. =head2 Store Plugins The backend into which session data is stored is provided by these plugins. For example, L uses a database table to store session data, while L uses L. =head2 Configuration First you need to load the appropriate plugins into your L application: package MyApp; use Catalyst qw/ Session Session::State::Cookie Session::Store::File /; This loads the session API, as well as the required backends of your choice. After the plugins are loaded they need to be configured. This is done according to L. Each backend plugin requires its own configuration options (with most plugins providing sensible defaults). The session API itself also has configurable options listed in L. For the plugins above we don't need any configuration at all - they should work out of the box, but suppose we did want to change some things around, it'll look like this: MyApp->config( 'Plugin::Session' => { cookie_name => "my_fabulous_cookie", storage => "/path/to/store_data_file", }); =head2 Usage Now, let's say we have an online shop, and the user is adding an item to the shopping cart. Typically the item the user was viewing would have a form or link that adds the item to the cart. Suppose this link goes to C, meaning that we want two units of the item C to be added to the cart. Our C action should look something like this: package MyApp::Controller::Cart; sub add : Local { my ( $self, $c, $item_id, $quantity ) = @_; $quantity ||= 1; if ( $c->model("Items")->item_exists($item_id) ) { $c->session->{cart}{$item_id} += $quantity; } else { die "No such item"; } } The way this works is that C<< $c->session >> always returns a hash reference to some data which is stored by the storage backend plugin. The hash reference returned always contains the same items that were in there at the end of the last request. All the mishmash described above is done automatically. First, the method looks to see if a session ID is set. This session ID will be set by the State plugin if appropriate, at the start of the request (e.g. by looking at the cookies sent by the client). If a session ID is set, the store will be asked to retrieve the session data for that specific session ID, and this is returned from C<< $c->session >>. This retrieval is cached, and will only happen once per request, if at all. If a session ID is not set, a new one is generated, a new anonymous hash is created and saved in the store with the session ID as the key, and the reference to the hash is returned. The action above takes this hash reference, and updates a nested hash within it, that counts quantity of each item as stored in the cart. Any cart-listing code can then look into the session data and use it to display the correct items, which will, of course, be remembered across requests. Here is an action some Template Toolkit example code that could be used to generate a cart listing: sub list_cart : Local { my ( $self, $c ) = @_; # get the cart data, that maps from item_id to quantity my $cart = $c->session->{cart} || {}; # this is our abstract model in which items are stored my $storage = $c->model("Items"); # map from item_id to item (an object or hash reference) my %items = map { $_ => $storage->get_item($_) } keys %$cart; # put the relevant info on the stash $c->stash->{cart}{items} = \%items; $c->stash->{cart}{quantity} = $cart; } And [a part of] the template it forwards to: [%# the table body lists all the items in the cart %] [% FOREACH item_id = cart.items.keys %] [%# each item has its own row in the table %] [% item = cart.items.$item_id %] [% quantity = cart.quantity.$item_id %] [% END %]
Item Quantity Price remove
[%# item.name is an attribute in the item # object, as loaded from the store %] [% item.name %] [%# supposedly this is part of a form where you # can update the quantity %] $ [% item.price * quantity %]
Total: [%# calculate sum in this cell - too # much headache for a tutorial ;-) %] Empty cart
As you can see the way that items are added into C<< $c->session->{cart} >> is pretty simple. Since C<< $c->session >> is restored as necessary, and contains data from previous requests by the same client, the cart can be updated as the user navigates the site pretty transparently. =head1 SECURITY ISSUES These issues all relate to how session data is managed, as described above. These are not issues you should be concerned about in your application code, but are here for their educational value. =head2 (Not) Trusting the Client In order to avoid the overhead of server-side data storage, the session data can be included in the cookie itself. There are two problems with this: =over 4 =item 1 The user can change the data. =item 2 Cookies have a 4 kilobyte size limit. The size limit is of no concern in this section, but data changing is. In the database scheme the data can be trusted, since the user can neither read nor write it. However, if the data is delegated to the user, then special measures have to be added for ensuring data integrity, and perhaps secrecy too. This can be implemented by encrypting and signing the cookie data, but this is a big headache. =back =head2 Session Hijacking What happens when client B says "I'm client A"? Well, basically, the server buys it. There's no real way around it. The solution is to make "I'm client A" a difficult thing to say. This is why session IDs are randomized. If they are properly randomized, session IDs are so hard to guess that they must be stolen instead. This is called session hijacking. There are several ways one might hijack another user's session. =head3 Cross Site Scripting One is by using cross site scripting attacks to steal the cookie data. In community sites, where users can cause the server to display arbitrary HTML, they can use this to put JavaScript code on the server. If the server does not enforce a strict subset of tags that may be used, the malicious user could use this code to steal the cookies (there is a JavaScript API that lets cookies be accessed, but this code has to be run on the same website that the cookie came from). =head3 Social Engineering By tricking a user into revealing a URI with session data embedded in it (when cookies are not used), the session ID can also be stolen. Also, a naive user could be tricked into showing the cookie data from the browser to a malicious user. =head1 AUTHOR Yuval Kogman Enothingmuch@woobling.orgE =cut Catalyst-Plugin-Session-0.43/lib/Catalyst/Plugin/Session/Store.pm000644 000000 000000 00000010157 14246413470 025056 0ustar00rootwheel000000 000000 package Catalyst::Plugin::Session::Store; use strict; use warnings; our $VERSION = '0.43'; $VERSION =~ tr/_//d; __PACKAGE__; __END__ =pod =head1 NAME Catalyst::Plugin::Session::Store - Base class for session storage drivers. =head1 SYNOPSIS package Catalyst::Plugin::Session::Store::MyBackend; use base qw/Catalyst::Plugin::Session::Store/; =head1 DESCRIPTION This class doesn't actually provide any functionality, but when the C module sets up it will check to see that C<< YourApp->isa("Catalyst::Plugin::Session::Store") >>. When you write a session storage plugin you should subclass this module for this reason. This documentation is intended for authors of session storage plugins, not for end users. =head1 WRITING STORE PLUGINS All session storage plugins need to adhere to the following interface specification to work correctly: =head2 Required Methods =over 4 =item get_session_data $key =item store_session_data $key, $data Retrieve or store session data by key. C<$data> is currently either a hash reference (for most keys) or an integer value (for expires), but all value types should be supported. Keys are in the format C, where C is C, C, or C, and C is always the session ID. Plugins such as L store extensions to this format, such as C. It is suggested that the store should split on the colon and store the data more efficiently - the API should remain stable, with the possible addition of new prefixes in the future. For example, C maps C a column of C by special-casing C and C for that key format, in order to ease the implementation of C. The only assurance stores are required to make is that given $c->store_session_data( $x, $y ); for any $x, $y == $c->get_session_data( $x ) will hold. =item store_session_data ( $key, $data ) Store a session whose KEY is the first parameter and data is the second parameter in storage. The second parameter is a hash reference, which should normally be serialized (and later deserialized by C). =item delete_session_data ( $key ) Delete the session whose KEY is the parameter. =item delete_expired_sessions This method is not called by any code at present, but may be called in the future, as part of a Catalyst-specific maintenance script. If you are wrapping around a backend which manages its own auto expiry you can just give this method an empty body. =back =head2 Error handling All errors should be thrown using L. Return values are not checked, and are assumed to be OK. Missing values are not errors. =head2 Auto-Expiry on the Backend Storage plugins are encouraged to use C<< $c->session_expires >>, C<< $c->config('Plugin::Session' => { expires => $val }) >>, or the storage of the C key to perform more efficient expiration, but only for the key prefixes C, C and C. If the backend chooses not to do so, L will detect expired sessions as they are retrieved and delete them if necessary. Note that session store that use this approach may leak disk space, since nothing will actively delete an expired session. The C method is there so that regularly scheduled maintenance scripts can give your backend the opportunity to clean up. =head2 Early Finalization By default the main session plugin will finalize during body finalization which ensures that all controller code related to the session has completed. However some storage plugins may wish to finalize earlier, during header finalization. For example a storage that saved state in a client cookie would wish this. If a storage plugin wants to finalize early it should set $c->_needs_early_session_finalization to true. Please note that if you do this in a storage plugin, you should warn users not to attempt to change or add session keys if you use a streaming or socket interface such as $c->res->write, $c->res->write_fh or $c->req->io_fh. =cut Catalyst-Plugin-Session-0.43/lib/Catalyst/Plugin/Session/State.pm000644 000000 000000 00000002363 14246413470 025042 0ustar00rootwheel000000 000000 package Catalyst::Plugin::Session::State; use strict; use warnings; our $VERSION = '0.43'; $VERSION =~ tr/_//d; __PACKAGE__; __END__ =pod =head1 NAME Catalyst::Plugin::Session::State - Base class for session state preservation plugins. =head1 SYNOPSIS package Catalyst::Plugin::Session::State::MyBackend; use base qw/Catalyst::Plugin::Session::State/; =head1 DESCRIPTION This class doesn't actually provide any functionality, but when the C module sets up it will check to see that C<< YourApp->isa("Catalyst::Plugin::Session::State") >>. When you write a session state plugin you should subclass this module this reason only. =head1 WRITING STATE PLUGINS To write a session state plugin you usually need to extend two methods: =over 4 =item prepare_(action|cookies|whatever) Set C (accessor) at B time using data in the request. Note that this must happen B other C instances, in order to get along with L. Overriding C is probably the stablest approach. =item finalize Modify the response at to include the session ID if C is defined, using whatever scheme you use. For example, set a cookie. =back =cut Catalyst-Plugin-Session-0.43/lib/Catalyst/Plugin/Session/Store/000755 000000 000000 00000000000 14246413532 024513 5ustar00rootwheel000000 000000 Catalyst-Plugin-Session-0.43/lib/Catalyst/Plugin/Session/Store/Dummy.pm000644 000000 000000 00000001642 14246413470 026150 0ustar00rootwheel000000 000000 package Catalyst::Plugin::Session::Store::Dummy; use base qw/Catalyst::Plugin::Session::Store/; use strict; use warnings; our $VERSION = '0.43'; $VERSION =~ tr/_//d; my %store; sub get_session_data { my ( $c, @keys ) = @_; @store{@keys}; } sub store_session_data { my $c = shift; my %data = @_; @store{ keys %data } = values %data; } sub delete_session_data { my ( $c, $sid ) = @_; delete $store{$sid}; } sub delete_expired_sessions { } __PACKAGE__; __END__ =pod =head1 NAME Catalyst::Plugin::Session::Store::Dummy - Doesn't really store sessions - useful for tests. =head1 SYNOPSIS use Catalyst qw/Session Session::Store::Dummy/; =head1 DESCRIPTION This plugin will "store" data in a hash. =head1 METHODS See L. =over 4 =item get_session_data =item store_session_data =item delete_session_data =item delete_expired_sessions =back =cut Catalyst-Plugin-Session-0.43/lib/Catalyst/Plugin/Session/Test/Store.pm000644 000000 000000 00000011165 14246413470 025775 0ustar00rootwheel000000 000000 package Catalyst::Plugin::Session::Test::Store; use strict; use warnings; our $VERSION = '0.43'; $VERSION =~ tr/_//d; use utf8; use Test::More; use File::Temp; use File::Spec; use Catalyst (); sub import { shift; my %args = @_; plan tests => 19 + ($args{extra_tests} || 0); my $backend = $args{backend}; my $cfg = $args{config}; my $p = "Session::Store::$backend"; use_ok( my $m = "Catalyst::Plugin::$p" ); isa_ok( bless( {}, $m ), "Catalyst::Plugin::Session::Store" ); { package # Hide from PAUSE Catalyst::Plugin::SessionStateTest; use base qw/Catalyst::Plugin::Session::State/; no strict 'refs'; sub get_session_id { my $c = shift; ${ ref($c) . "::session_id" }; } sub set_session_id { my ( $c, $sid ) = @_; ${ ref($c) . "::session_id" } = $sid; } sub delete_session_id { my $c = shift; undef ${ ref($c) . "::session_id" }; } } { package # Hide from PAUSE SessionStoreTest; use Catalyst qw/Session SessionStateTest/; push our (@ISA), $m; our $VERSION # make unparseable = "123"; # Do not remove use strict; use warnings; use Test::More; sub create_session : Global { my ( $self, $c ) = @_; ok( !$c->session_is_valid, "no session id yet" ); ok( $c->session, "session created" ); ok( $c->session_is_valid, "with a session id" ); $c->session->{magic} = "møøse"; } sub recover_session : Global { my ( $self, $c ) = @_; ok( $c->session_is_valid, "session id exists" ); is( $c->sessionid, our $session_id, "and is the one we saved in the last action" ); ok( $c->session, "a session exists" ); is( $c->session->{magic}, "møøse", "and it contains what we put in on the last attempt" ); $c->delete_session("user logout"); } sub after_session : Global { my ( $self, $c ) = @_; ok( !$c->session_is_valid, "no session id" ); ok( !$c->session->{magic}, "session data not restored" ); ok( !$c->session_delete_reason, "no reason for deletion" ); } @{ __PACKAGE__->config->{'Plugin::Session'} }{ keys %$cfg } = values %$cfg; { __PACKAGE__->setup; }; # Extra block here is an INSANE HACK to get inlined constructor # (i.e. to make B::Hooks::EndOfScope fire) } { package # Hide from PAUSE SessionStoreTest2; use Catalyst qw/Session SessionStateTest/; push our (@ISA), $m; our $VERSION # make unparseable = "123"; use Test::More; sub create_session : Global { my ( $self, $c ) = @_; $c->session->{magic} = "møøse"; } sub recover_session : Global { my ( $self, $c ) = @_; ok( !$c->session_is_valid, "session is gone" ); is( $c->session_delete_reason, "session expired", "reason is that the session expired" ); ok( !$c->session->{magic}, "no saved data" ); } __PACKAGE__->config->{'Plugin::Session'}{expires} = 0; @{ __PACKAGE__->config->{'Plugin::Session'} }{ keys %$cfg } = values %$cfg; { __PACKAGE__->setup; }; # INSANE HACK (the block - as above) } use Test::More; can_ok( $m, "get_session_data" ); can_ok( $m, "store_session_data" ); can_ok( $m, "delete_session_data" ); can_ok( $m, "delete_expired_sessions" ); { package # Hide from PAUSE t1; use Catalyst::Test "SessionStoreTest"; # idiotic void context warning workaround my $x = get("/create_session"); $x = get("/recover_session"); $x = get("/after_session"); } { package # Hide fram PAUSE t2; use Catalyst::Test "SessionStoreTest2"; my $x = get("/create_session"); sleep 1; # let the session expire $x = get("/recover_session"); } } __PACKAGE__; __END__ =pod =head1 NAME Catalyst::Plugin::Session::Test::Store - Reusable sanity for session storage engines. =head1 SYNOPSIS #!/usr/bin/perl use Catalyst::Plugin::Session::Test::Store ( backend => "FastMmap", config => { storage => "/tmp/foo", }, ); =head1 DESCRIPTION =cut Catalyst-Plugin-Session-0.43/xt/pod.t000644 000000 000000 00000000071 14245255154 017607 0ustar00rootwheel000000 000000 use Test::More; use Test::Pod 1.14; all_pod_files_ok(); Catalyst-Plugin-Session-0.43/xt/podcoverage.t000644 000000 000000 00000000106 14245255154 021322 0ustar00rootwheel000000 000000 use Test::More; use Test::Pod::Coverage 1.04; all_pod_coverage_ok(); Catalyst-Plugin-Session-0.43/t/cat_test.t000644 000000 000000 00000003204 14245425001 020432 0ustar00rootwheel000000 000000 use strict; use warnings; use Test::Needs { 'Catalyst::Plugin::Authentication' => '0', 'Catalyst::Plugin::Session::State::Cookie' => '0.03', }; use Test::More; use HTTP::Request::Common; use lib "t/lib"; use Catalyst::Test 'SessionTestApp'; my $res; $res = request(POST 'http://localhost/login', [username => 'bob', password => 's00p3r', remember => 1]); is($res->code, 200, 'succeeded'); my $cookie = $res->header('Set-Cookie'); ok($cookie, 'Have a cookie'); # cookie is changed by the get sleep(1); $res = request(GET 'http://localhost/page', Cookie => $cookie); like($res->content, qr/logged in/, 'logged in'); my $new_cookie = $res->header('Set-Cookie'); isnt( $cookie, $new_cookie, 'cookie expires has been updated' ); # request with no cookie $res = request(GET 'http://localhost/page' ); like($res->content, qr/please login/, 'not logged in'); $new_cookie = $res->header('Set-Cookie'); ok( ! defined $new_cookie, 'no cookie created' ); # check that cookie is reset by reset_session_expires $res = request(GET 'http://localhost/reset_session_expires', Cookie => $cookie); my $reset_cookie = $res->header('Set-Cookie'); isnt( $cookie, $reset_cookie, 'Cookie has been changed by reset_session' ); # this checks that cookie exists after a logout and redirect # Catalyst::Plugin::Authentication removes the user session (remove_persisted_user) $res = request(GET 'http://localhost/logout_redirect', Cookie => $cookie); is($res->code, 302, 'redirected'); is($res->header('Location'), 'http://localhost/from_logout_redirect', 'Redirected after logout_redirect'); ok($res->header('Set-Cookie'), 'Cookie is there after redirect'); done_testing; Catalyst-Plugin-Session-0.43/t/05_semi_persistent_flash.t000644 000000 000000 00000002052 14245255735 023541 0ustar00rootwheel000000 000000 use strict; use warnings; use Test::Needs { 'Catalyst::Plugin::Session::State::Cookie' => '0.03', }; use Test::More; use lib "t/lib"; use MiniUA; my $ua = MiniUA->new('FlashTestApp'); my $res; # flash absent for initial request $res = $ua->get( "http://localhost/first" ); ok +$res->is_success; like +$res->content, qr{flash is not set}, "not set"; # present for 1st req. $res = $ua->get( "http://localhost/second"); ok +$res->is_success; like +$res->content, qr{flash set first time}, "set first"; # should be the same 2nd req. $res = $ua->get( "http://localhost/third"); ok +$res->is_success; like +$res->content, qr{flash set second time}, "set second"; # and the third request, flash->{is_set} has the same value as 2nd. $res = $ua->get( "http://localhost/fourth"); ok +$res->is_success; like +$res->content, qr{flash set 3rd time, same val as prev.}, "set third"; # and should be absent again for the 4th req. $res = $ua->get( "http://localhost/fifth"); ok +$res->is_success; like +$res->content, qr{flash is not}, "flash has gone"; done_testing; Catalyst-Plugin-Session-0.43/t/session_valid.t000644 000000 000000 00000001055 14245255735 021507 0ustar00rootwheel000000 000000 use strict; use warnings; use Test::Needs { 'Catalyst::Plugin::Session::State::Cookie' => '0.03', }; use Test::More; use lib "t/lib"; use MiniUA; my $ua = MiniUA->new('SessionValid'); my $res; $res = $ua->get( "http://localhost/" ); ok +$res->is_success, "initial get"; like +$res->content, qr{value set}, "page contains expected value"; sleep 2; $res = $ua->get( "http://localhost/" ); ok +$res->is_success, "grab the page again, after the session has expired"; like +$res->content, qr{value set}, "page contains expected value"; done_testing; Catalyst-Plugin-Session-0.43/t/01_setup.t000644 000000 000000 00000003600 14245255735 020303 0ustar00rootwheel000000 000000 use strict; use warnings; use Test::More; use Class::MOP; use Test::Deep; use Catalyst::Plugin::Session; my %config; my $log_meta = Class::MOP::Class->create_anon_class(superclasses => ['Moose::Object']); my $log = $log_meta->name->new; my @mock_isa = (); my $calls = 0; $log_meta->add_method("fatal" => sub { $calls++; 1; }); { package MockCxt; use MRO::Compat; use base qw(Catalyst::Plugin::Session); sub new { bless {}, $_[0] } sub config { \%config } sub log { $log } sub isa { my $self = shift; my $class = shift; grep { $_ eq $class } @mock_isa or $self->SUPER::isa($class); } } can_ok( 'Catalyst::Plugin::Session', "setup" ); eval { MockCxt->new->setup }; # throws OK is not working with NEXT like( $@, qr/requires.*((?:State|Store).*){2}/i, "can't setup an object that doesn't use state/store plugins" ); is $calls, 1, 'Fatal error logged'; @mock_isa = qw/Catalyst::Plugin::Session::State/; eval { MockCxt->new->setup }; like( $@, qr/requires.*(?:Store)/i, "can't setup an object that doesn't use state/store plugins" ); @mock_isa = qw/Catalyst::Plugin::Session::Store/; eval { MockCxt->new->setup }; like( $@, qr/requires.*(?:State)/i, "can't setup an object that doesn't use state/store plugins" ); $calls = 0; @mock_isa = qw/Catalyst::Plugin::Session::State Catalyst::Plugin::Session::Store/; eval { MockCxt->new->setup }; ok( !$@, "setup() lives with state/store plugins in use" ); is( $calls, 0, "no fatal error logged either" ); cmp_deeply( [ keys %{ $config{'Plugin::Session'} } ], bag(qw/expires verify_address verify_user_agent expiry_threshold/), "default values for config were populated in successful setup", ); %config = ( session => { expires => 1234 } ); MockCxt->new->setup; is( $config{session}{expires}, 1234, "user values are not overwritten in config" ); done_testing; Catalyst-Plugin-Session-0.43/t/live_verify_address.t000644 000000 000000 00000002436 14245424777 022705 0ustar00rootwheel000000 000000 use strict; use warnings; use Test::Needs { 'Catalyst::Plugin::Authentication' => '0', 'Catalyst::Plugin::Session::State::Cookie' => '0.03', }; use Test::More; use lib "t/lib"; use MiniUA; my $ua = MiniUA->new('SessionTestApp'); # Test without delete __address local $ENV{REMOTE_ADDR} = "192.168.1.1"; my $res; $res = $ua->get( "http://localhost/login" ); ok +$res->is_success; like +$res->content, qr{logged in}; $res = $ua->get( "http://localhost/set_session_variable/logged/in" ); ok +$res->is_success; like +$res->content, qr{session variable set}; # Change Client my $ua2 = MiniUA->new('SessionTestApp'); $res = $ua2->get( "http://localhost/get_session_variable/logged" ); ok +$res->is_success; like +$res->content, qr{VAR_logged=n\.a\.}; # Inital Client local $ENV{REMOTE_ADDR} = "192.168.1.1"; $res = $ua->get( "http://localhost/login_without_address" ); ok +$res->is_success; like +$res->content, qr{logged in \(without address\)}; $res = $ua->get( "http://localhost/set_session_variable/logged/in" ); ok +$res->is_success; like +$res->content, qr{session variable set}; # Change Client local $ENV{REMOTE_ADDR} = "192.168.1.2"; $res = $ua->get( "http://localhost/get_session_variable/logged" ); ok +$res->is_success; like +$res->content, qr{VAR_logged=in}; done_testing; Catalyst-Plugin-Session-0.43/t/00_basic_sanity.t000644 000000 000000 00000000255 14245255735 021615 0ustar00rootwheel000000 000000 use strict; use warnings; use Test::More; use Catalyst::Plugin::Session; can_ok('Catalyst::Plugin::Session', qw/sessionid session session_delete_reason/); done_testing; Catalyst-Plugin-Session-0.43/t/live_accessor.t000644 000000 000000 00000001035 14245424776 021467 0ustar00rootwheel000000 000000 use strict; use warnings; use Test::Needs { 'Catalyst::Plugin::Authentication' => '0', 'Catalyst::Plugin::Session::State::Cookie' => '0.03', }; use Test::More; use lib "t/lib"; use MiniUA; my $ua = MiniUA->new('SessionTestApp'); my $res = $ua->get( '/accessor_test'); ok +$res->is_success, 'Set session vars okay'; like +$res->content, qr{two: 2}, 'k/v list setter works okay'; like +$res->content, qr{four: 4}, 'hashref setter works okay'; like +$res->content, qr{five: 5}, 'direct access works okay'; done_testing; Catalyst-Plugin-Session-0.43/t/03_flash.t000644 000000 000000 00000003776 14245255735 020260 0ustar00rootwheel000000 000000 use strict; use warnings; use Test::More; use Test::Deep; use Catalyst::Plugin::Session; my $c_meta = Class::MOP::Class->create_anon_class( superclasses => [ 'Catalyst::Plugin::Session', 'Moose::Object', ], ); my $c = $c_meta->name->new; my $flash = {}; $c_meta->add_method( get_session_data => sub { my ( $c, $key ) = @_; return $key =~ /expire/ ? time() + 1000 : $flash; }, ); $c->meta->add_method("debug" => sub { 0 }); $c->meta->add_method("store_session_data" => sub { $flash = $_[2] }); $c->meta->add_method("delete_session_data" => sub { $flash = {} }); $c->meta->add_method( _sessionid => sub { "deadbeef" }); my $config = { expires => 1000 }; $c->meta->add_method( config => sub { { session => $config } }); my $stash = {}; $c->meta->add_method( stash => sub { $stash } ); is_deeply( $c->session, {}, "nothing in session" ); is_deeply( $c->flash, {}, "nothing in flash" ); $c->flash->{foo} = "moose"; $c->finalize_body; is_deeply( $c->flash, { foo => "moose" }, "one key in flash" ); cmp_deeply( $c->session, { __updated => re('^\d+$'), __flash => $c->flash }, "session has __flash with flash data" ); $c->flash(bar => "gorch"); is_deeply( $c->flash, { foo => "moose", bar => "gorch" }, "two keys in flash" ); cmp_deeply( $c->session, { __updated => re('^\d+$'), __flash => $c->flash }, "session still has __flash with flash data" ); $c->finalize_body; is_deeply( $c->flash, { bar => "gorch" }, "one key in flash" ); $c->finalize_body; $c->flash->{test} = 'clear_flash'; $c->finalize_body; $c->clear_flash(); is_deeply( $c->flash, {}, "nothing in flash after clear_flash" ); $c->finalize_body; is_deeply( $c->flash, {}, "nothing in flash after finalize after clear_flash" ); cmp_deeply( $c->session, { __updated => re('^\d+$'), }, "session has empty __flash after clear_flash + finalize" ); $c->flash->{bar} = "gorch"; $config->{flash_to_stash} = 1; $c->finalize_body; $c->prepare_action; is_deeply( $c->stash, { bar => "gorch" }, "flash copied to stash" ); done_testing; Catalyst-Plugin-Session-0.43/t/lib/000755 000000 000000 00000000000 14246413532 017215 5ustar00rootwheel000000 000000 Catalyst-Plugin-Session-0.43/t/live_session_fixation.t000644 000000 000000 00000005307 14245425004 023240 0ustar00rootwheel000000 000000 use strict; use warnings; use Test::Needs { 'Catalyst::Plugin::Authentication' => '0', 'Catalyst::Plugin::Session::State::Cookie' => '0.03', }; use Test::More; use lib "t/lib"; use MiniUA; #try completely random cookie unknown for our application; should be rejected my $cookie_name = 'sessiontestapp_session'; my $cookie_value = '89c3a019866af6f5a305e10189fbb23df3f4772c'; my ( @injected_cookie ) = ( 1, $cookie_name , $cookie_value ,'/', undef, 0, undef, undef, undef, {} ); my $injected_cookie_str = "${cookie_name}=${cookie_value}"; my $ua1 = MiniUA->new('SessionTestApp'); $ua1->cookie_jar->set_cookie( @injected_cookie ); my $res = $ua1->get( "http://localhost/login" ); my $cookie1 = $res->header('Set-Cookie'); ok $cookie1, "Set-Cookie 1"; isnt $cookie1, qr/$injected_cookie_str/, "Logging in generates us a new cookie"; $res = $ua1->get( "http://localhost/get_sessid" ); my $sid1 = $res->content; #set session variable var1 before session id change $ua1->get( "http://localhost/set_session_variable/var1/set_before_change"); $res = $ua1->get( "http://localhost/get_session_variable/var1"); is +$res->content, 'VAR_var1=set_before_change'; #just diagnostic dump #diag "Before-change:".$ua1->get( "http://localhost/dump_session" )->content; #change session id; all session data should be kept; old session id invalidated $res = $ua1->get( "http://localhost/change_sessid" ); my $cookie2 = $res->header('Set-Cookie'); ok $cookie2, "Set-Cookie 2"; isnt $cookie2, $cookie1, "Cookie changed"; $res = $ua1->get( "http://localhost/get_sessid" ); my $sid2 = $res->content; isnt $sid2, $sid1, 'SID changed'; #just diagnostic dump #diag "After-change:".$ua1->get( "http://localhost/dump_session" )->content; #set session variable var2 after session id change $ua1->get( "http://localhost/set_session_variable/var2/set_after_change"); #check if var1 and var2 contain expected values $res = $ua1->get( "http://localhost/get_session_variable/var1"); is +$res->content, 'VAR_var1=set_before_change'; $res = $ua1->get( "http://localhost/get_session_variable/var2"); is +$res->content, 'VAR_var2=set_after_change'; #just diagnostic dump #diag "End1".$ua1->get( "http://localhost/dump_session" )->content; #try to use old cookie value (before session_id_change) my $ua2 = MiniUA->new('SessionTestApp'); $ua2->cookie_jar->set_cookie( @injected_cookie ); #if we take old cookie we should not be able to get any old session data $res = $ua2->get( "http://localhost/get_session_variable/var1"); is +$res->content, 'VAR_var1=n.a.'; $res = $ua2->get( "http://localhost/get_session_variable/var2"); is +$res->content, 'VAR_var2=n.a.'; #just diagnostic dump #diag "End2".$ua1->get( "http://localhost/dump_session" )->content; done_testing; Catalyst-Plugin-Session-0.43/t/live_expiry_threshold.t000644 000000 000000 00000003703 14245425046 023254 0ustar00rootwheel000000 000000 use strict; use warnings; use Test::Needs { 'Catalyst::Plugin::Authentication' => '0', 'Catalyst::Plugin::Session::State::Cookie' => '0.03', }; use Test::More; use lib "t/lib"; use MiniUA; my $ua = MiniUA->new('SessionExpiry'); my $res = $ua->get( "http://localhost/session_data_expires" ); ok($res->is_success, "session_data_expires"); my $expiry = $res->decoded_content + 0; $res = $ua->get( "http://localhost/session_expires" ); ok($res->is_success, "session_expires"); is($res->decoded_content, $expiry, "session_expires == session_data_expires"); sleep(1); $res = $ua->get( "http://localhost/session_data_expires" ); ok($res->is_success, "session_data_expires"); is($res->decoded_content, $expiry, "expiration not updated"); $res = $ua->get( "http://localhost/session_expires" ); ok($res->is_success, "session_expires"); is($res->decoded_content, $expiry, "session_expires == session_data_expires"); # $res = $ua->get( "http://localhost/update_session" ); ok($res->is_success, "update_session"); $res = $ua->get( "http://localhost/session_data_expires" ); ok($res->is_success, "session_data_expires"); my $updated = $res->decoded_content + 0; ok($updated > $expiry, "expiration updated"); $expiry = $updated; $res = $ua->get( "http://localhost/session_data_expires" ); ok($res->is_success, "session_data_expires"); is($res->decoded_content, $expiry, "expiration not updated"); $res = $ua->get( "http://localhost/session_expires" ); ok($res->is_success, "session_expires"); is($res->decoded_content, $expiry, "session_expires == session_data_expires"); sleep(10); $res = $ua->get( "http://localhost/session_data_expires" ); ok($res->is_success, "session_data_expires"); $updated = $res->decoded_content + 0; ok($updated > $expiry, "expiration updated"); $res = $ua->get( "http://localhost/session_expires" ); ok($res->is_success, "session_expires"); is($res->decoded_content, $updated, "session_expires == session_data_expires"); done_testing; Catalyst-Plugin-Session-0.43/t/live_app.t000644 000000 000000 00000010206 14245424623 020434 0ustar00rootwheel000000 000000 use strict; use warnings; use Test::Needs { 'Catalyst::Plugin::Authentication' => '0', 'Catalyst::Plugin::Session::State::Cookie' => '0.03', }; use Test::More; use lib "t/lib"; use MiniUA; my $ua1 = MiniUA->new('SessionTestApp'); my $ua2 = MiniUA->new('SessionTestApp'); my $res1 = $ua1->get( 'http://localhost/page'); my $res2 = $ua2->get( 'http://localhost/page'); ok $_->is_success, 'initial get' for $res1, $res2; like $res1->content, qr/please login/, 'ua1 not logged in'; like $res2->content, qr/please login/, 'ua2 not logged in'; $res1 = $ua1->get( 'http://localhost/login'); ok $res1->is_success, 'log ua1 in'; like $res1->content, qr/logged in/, 'ua1 logged in'; $res1 = $ua1->get( 'http://localhost/page'); $res2 = $ua2->get( 'http://localhost/page'); ok $_->is_success, 'get main page' for $res1, $res2; like $res1->content, qr/you are logged in/, 'ua1 logged in'; like $res2->content, qr/please login/, 'ua2 not logged in'; $res2 = $ua2->get( 'http://localhost/login'); ok $res2->is_success, 'log ua2 in'; like $res2->content, qr/logged in/, 'ua2 logged in'; $res1 = $ua1->get( 'http://localhost/page'); $res2 = $ua2->get( 'http://localhost/page'); ok $_->is_success, 'get main page' for $res1, $res2; like $res1->content, qr/you are logged in/, 'ua1 logged in'; like $res2->content, qr/you are logged in/, 'ua2 logged in'; my ( $u1_expires ) = ($res1->content =~ /(\d+)$/); my ( $u2_expires ) = ($res2->content =~ /(\d+)$/); sleep 1; $res1 = $ua1->get( 'http://localhost/page'); $res2 = $ua2->get( 'http://localhost/page'); ok $_->is_success, 'get main page' for $res1, $res2; like $res1->content, qr/you are logged in/, 'ua1 logged in'; like $res2->content, qr/you are logged in/, 'ua2 logged in'; my ( $u1_expires_updated ) = ($res1->content =~ /(\d+)$/); my ( $u2_expires_updated ) = ($res2->content =~ /(\d+)$/); cmp_ok( $u1_expires, "<", $u1_expires_updated, "expiry time updated"); cmp_ok( $u2_expires, "<", $u2_expires_updated, "expiry time updated"); $res2 = $ua2->get( 'http://localhost/logout'); ok $res2->is_success, 'log ua2 out'; like $res2->content, qr/logged out/, 'ua2 logged out'; like $res2->content, qr/after 2 requests/, 'ua2 made 2 requests for page in the session'; $res1 = $ua1->get( 'http://localhost/page'); $res2 = $ua2->get( 'http://localhost/page'); ok $_->is_success, 'get main page' for $res1, $res2; like $res1->content, qr/you are logged in/, 'ua1 logged in'; like $res2->content, qr/please login/, 'ua2 not logged in'; $res1 = $ua1->get( 'http://localhost/logout'); ok $res1->is_success, 'log ua1 out'; like $res1->content, qr/logged out/, 'ua1 logged out'; like $res1->content, qr/after 4 requests/, 'ua1 made 4 requests for page in the session'; $res1 = $ua1->get( 'http://localhost/page'); $res2 = $ua2->get( 'http://localhost/page'); ok $_->is_success, 'get main page' for $res1, $res2; like $res1->content, qr/please login/, 'ua1 not logged in'; like $res2->content, qr/please login/, 'ua2 not logged in'; my $ua3 = MiniUA->new('SessionTestApp'); my $res3 = $ua3->get( 'http://localhost/login'); ok $res3->is_success, 'log ua3 in'; $res3 = $ua3->get( 'http://localhost/dump_these_loads_session'); ok $res3->is_success; like $res3->content, qr/NOT/; my $ua4 = MiniUA->new('SessionTestApp'); my $res4 = $ua4->get( 'http://localhost/page'); ok $res4->is_success, 'initial get'; like $res4->content, qr/please login/, 'ua4 not logged in'; $res4 = $ua4->get( 'http://localhost/login'); ok $res4->is_success, 'log ua4 in'; like $res4->content, qr/logged in/, 'ua4 logged in'; $res4 = $ua4->get( "http://localhost/page"); ok +$res4->is_success, "get page"; my ( $ua4_expires1 ) = ($res4->content =~ /(\d+)$/); $res4 = $ua4->get( "http://localhost/page"); ok +$res4->is_success, "get page"; my ( $ua4_expires2 ) = ($res4->content =~ /(\d+)$/); is( $ua4_expires1, $ua4_expires2, 'expires has not changed' ); $res4 = $ua4->get( "http://localhost/change_session_expires"); ok +$res4->is_success, "get page"; $res4 = $ua4->get( "http://localhost/page" ); ok +$res4->is_success, "get page"; my ( $ua4_expires3 ) = ($res4->content =~ /(\d+)$/); ok( $ua4_expires3 > ( $ua4_expires1 + 30000000), 'expires has been extended' ); done_testing; Catalyst-Plugin-Session-0.43/t/live_verify_user_agent.t000644 000000 000000 00000002364 14245424605 023402 0ustar00rootwheel000000 000000 use strict; use warnings; use Test::Needs { 'Catalyst::Plugin::Authentication' => '0', 'Catalyst::Plugin::Session::State::Cookie' => '0.03', }; use Test::More; use lib "t/lib"; use MiniUA; my $ua = MiniUA->new('SessionTestApp'); $ua->agent('Initial user_agent'); my $res; $res = $ua->get( "http://localhost/user_agent" ); ok +$res->is_success, "get initial user_agent"; like +$res->content, qr{UA=Initial user_agent}, "test initial user_agent"; $res = $ua->get( "http://localhost/page" ); ok +$res->is_success, "initial get main page"; like +$res->content, qr{please login}, "ua not logged in"; $res = $ua->get( "http://localhost/login" ); ok +$res->is_success, "log ua in"; like +$res->content, qr{logged in}, "ua logged in"; $res = $ua->get( "http://localhost/page" ); ok +$res->is_success, "get main page"; like +$res->content, qr{you are logged in}, "ua logged in"; $ua->agent('Changed user_agent'); $res = $ua->get( "http://localhost/user_agent" ); ok +$res->is_success, "get changed user_agent"; like +$res->content, qr{UA=Changed user_agent}, "test changed user_agent"; $res = $ua->get( "http://localhost/page" ); ok +$res->is_success, "test deleted session"; like +$res->content, qr{please login}, "ua not logged in"; done_testing; Catalyst-Plugin-Session-0.43/t/lib/FlashTestApp/000755 000000 000000 00000000000 14246413532 021553 5ustar00rootwheel000000 000000 Catalyst-Plugin-Session-0.43/t/lib/MiniUA.pm000644 000000 000000 00000002271 14245255735 020707 0ustar00rootwheel000000 000000 package MiniUA; use strict; use warnings; use Plack::Test (); use HTTP::Cookies; use HTTP::Request::Common; sub new { my ($class, $app, $opts) = @_; my $psgi = ref $app eq 'CODE' ? $app : do { eval "require $app;" or die $@; $app->psgi_app; }; $opts ||= {}; my $self = bless { psgi => $psgi, plack_test => Plack::Test->create($psgi), cookie_jar => HTTP::Cookies->new(hide_cookie2 => 1), headers => $opts, }, $class; return $self; } sub agent { my $self = shift; if (@_) { return $self->{headers}{'User-Agent'} = shift; } return $self->{headers}{'User-Agent'}; } sub cookie_jar { my $self = shift; return $self->{cookie_jar}; } sub request { my ($self, $req) = @_; my $pt = $self->{plack_test}; my $jar = $self->cookie_jar; my $headers = $self->{headers}; my $uri = $req->uri; $uri->scheme('http') unless defined $uri->scheme; $uri->host('localhost') unless defined $uri->host; $req->header(%$headers) if %$headers; $jar->add_cookie_header($req); my $res = $pt->request($req); $jar->extract_cookies($res); return $res; } sub get { my ($self, $url) = @_; $self->request(GET $url); } 1; Catalyst-Plugin-Session-0.43/t/lib/SessionExpiry.pm000644 000000 000000 00000000456 13673241434 022407 0ustar00rootwheel000000 000000 package SessionExpiry; use Catalyst qw/Session Session::Store::Dummy Session::State::Cookie Authentication/; use strict; use warnings; __PACKAGE__->config( 'Plugin::Session' => { expires => 20, expiry_threshold => 10, }, ); __PACKAGE__->setup; __PACKAGE__; Catalyst-Plugin-Session-0.43/t/lib/FlashTestApp.pm000644 000000 000000 00000000234 14245425141 022106 0ustar00rootwheel000000 000000 package FlashTestApp; use Catalyst qw/Session Session::Store::Dummy Session::State::Cookie/; use strict; use warnings; __PACKAGE__->setup; __PACKAGE__; Catalyst-Plugin-Session-0.43/t/lib/SessionTestApp.pm000644 000000 000000 00000001643 14245424764 022513 0ustar00rootwheel000000 000000 package SessionTestApp; use Catalyst qw/Session Session::Store::Dummy Session::State::Cookie Authentication/; use strict; use warnings; __PACKAGE__->config('Plugin::Session' => { # needed for live_verify_user_agent.t; should be harmless for other tests verify_user_agent => 1, verify_address => 1, }, 'Plugin::Authentication' => { default => { credential => { class => 'Password', password_field => 'password', password_type => 'clear' }, store => { class => 'Minimal', users => { bob => { password => "s00p3r", }, william => { password => "s3cr3t", }, }, }, }, }, ); __PACKAGE__->setup; __PACKAGE__; Catalyst-Plugin-Session-0.43/t/lib/SessionExpiry/000755 000000 000000 00000000000 14246413532 022041 5ustar00rootwheel000000 000000 Catalyst-Plugin-Session-0.43/t/lib/SessionValid.pm000644 000000 000000 00000000367 13673241434 022167 0ustar00rootwheel000000 000000 package SessionValid; use Catalyst qw/Session Session::Store::Dummy Session::State::Cookie/; use strict; use warnings; __PACKAGE__->config('Plugin::Session' => { cookie_expires => 0, expires => 1, }); __PACKAGE__->setup; __PACKAGE__; Catalyst-Plugin-Session-0.43/t/lib/SessionValid/000755 000000 000000 00000000000 14246413532 021620 5ustar00rootwheel000000 000000 Catalyst-Plugin-Session-0.43/t/lib/SessionTestApp/000755 000000 000000 00000000000 14246413532 022141 5ustar00rootwheel000000 000000 Catalyst-Plugin-Session-0.43/t/lib/SessionTestApp/Controller/000755 000000 000000 00000000000 14246413532 024264 5ustar00rootwheel000000 000000 Catalyst-Plugin-Session-0.43/t/lib/SessionTestApp/Controller/Root.pm000644 000000 000000 00000005564 13673241434 025562 0ustar00rootwheel000000 000000 package SessionTestApp::Controller::Root; use strict; use warnings; use Data::Dumper; use base qw/Catalyst::Controller/; __PACKAGE__->config( namespace => '' ); sub login : Global { my ( $self, $c ) = @_; $c->session; $c->res->output("logged in"); } sub login_without_address : Global { my ( $self, $c ) = @_; $c->session; $c->log->debug($c->request->address); delete $c->session->{__address}; $c->res->output("logged in (without address)"); } sub logout : Global { my ( $self, $c ) = @_; $c->res->output( "logged out after " . $c->session->{counter} . " requests" ); $c->delete_session("logout"); } sub logout_redirect : Global { my ( $self, $c ) = @_; $c->logout; $c->res->output("redirect from here"); $c->res->redirect( $c->uri_for('from_logout_redirect') ); } sub from_logout_redirect : Global { my ( $self, $c ) = @_; $c->res->output( "got here from logout_redirect" ); } sub set_session_variable : Global { my ( $self, $c, $var, $val ) = @_; $c->session->{$var} = $val; $c->res->output("session variable set"); } sub get_session_variable : Global { my ( $self, $c, $var ) = @_; my $val = $c->session->{$var} || 'n.a.'; $c->res->output("VAR_$var=$val"); } sub get_sessid : Global { my ( $self, $c ) = @_; my $sid = $c->sessionid || 'n.a.'; $c->res->output("SID=$sid"); } sub dump_session : Global { my ( $self, $c ) = @_; my $sid = $c->sessionid || 'n.a.'; my $dump = Dumper($c->session || 'n.a.'); $c->res->output("[SID=$sid]\n$dump"); } sub change_sessid : Global { my ( $self, $c ) = @_; $c->change_session_id; $c->res->output("session id changed"); } sub page : Global { my ( $self, $c ) = @_; if ( $c->session_is_valid ) { $c->res->output("you are logged in, session expires at " . $c->session_expires); $c->session->{counter}++; } else { $c->res->output("please login"); } } sub user_agent : Global { my ( $self, $c ) = @_; $c->res->output('UA=' . $c->req->user_agent); } sub accessor_test : Global { my ( $self, $c ) = @_; $c->session( one => 1, two => 2, ); $c->session( { three => 3, four => 4, }, ); $c->session->{five} = 5; for my $key (keys %{ $c->session }) { $c->res->write("$key: " . $c->session->{$key} . "\n"); } } sub dump_these_loads_session : Global { my ($self, $c) = @_; $c->dump_these(); if ($c->_session) { $c->res->write('LOADED') } else { $c->res->write('NOT'); } } sub change_session_expires : Global { my ($self, $c) = @_; $c->change_session_expires(31536000); $c->res->output($c->session_expires); } sub reset_session_expires : Global { my ($self, $c) = @_; $c->reset_session_expires; $c->res->output($c->session_expires); } 1; Catalyst-Plugin-Session-0.43/t/lib/SessionValid/Controller/000755 000000 000000 00000000000 14246413532 023743 5ustar00rootwheel000000 000000 Catalyst-Plugin-Session-0.43/t/lib/SessionValid/Controller/Root.pm000644 000000 000000 00000000470 13673241434 025230 0ustar00rootwheel000000 000000 package SessionValid::Controller::Root; use strict; use warnings; use base qw/Catalyst::Controller/; __PACKAGE__->config( namespace => '' ); sub index :Path :Args(0) { my ( $self, $c ) = @_; $c->session('value' => 'value set'); $c->session_is_valid; $c->res->body($c->session->{value}); } 1; Catalyst-Plugin-Session-0.43/t/lib/SessionExpiry/Controller/000755 000000 000000 00000000000 14246413532 024164 5ustar00rootwheel000000 000000 Catalyst-Plugin-Session-0.43/t/lib/SessionExpiry/Controller/Root.pm000644 000000 000000 00000001200 13673241434 025441 0ustar00rootwheel000000 000000 package SessionExpiry::Controller::Root; use strict; use warnings; use base qw/Catalyst::Controller/; __PACKAGE__->config( namespace => '' ); sub session_data_expires : Global { my ( $self, $c ) = @_; $c->session; if (my $sid = $c->sessionid) { $c->finalize_headers(); # force expiration to be updated $c->res->output($c->get_session_data("expires:$sid")); } } sub session_expires : Global { my ($self, $c) = @_; $c->session; $c->res->output($c->session_expires); } sub update_session : Global { my ($self, $c) = @_; $c->session->{foo} ++; $c->res->output($c->session->{foo}); } Catalyst-Plugin-Session-0.43/t/lib/FlashTestApp/Controller/000755 000000 000000 00000000000 14246413532 023676 5ustar00rootwheel000000 000000 Catalyst-Plugin-Session-0.43/t/lib/FlashTestApp/Controller/Root.pm000644 000000 000000 00000002157 14245256141 025164 0ustar00rootwheel000000 000000 package FlashTestApp::Controller::Root; use strict; use warnings; use Data::Dumper; use base qw/Catalyst::Controller/; __PACKAGE__->config( namespace => '' ); no warnings 'uninitialized'; sub default : Private { my ($self, $c) = @_; $c->session; } sub first : Global { my ( $self, $c ) = @_; if ( ! $c->flash->{is_set}) { $c->stash->{message} = "flash is not set"; $c->flash->{is_set} = 1; } } sub second : Global { my ( $self, $c ) = @_; if ($c->flash->{is_set} == 1){ $c->stash->{message} = "flash set first time"; $c->flash->{is_set}++; } } sub third : Global { my ( $self, $c ) = @_; if ($c->flash->{is_set} == 2) { $c->stash->{message} = "flash set second time"; $c->keep_flash("is_set"); } } sub fourth : Global { my ( $self, $c ) = @_; if ($c->flash->{is_set} == 2) { $c->stash->{message} = "flash set 3rd time, same val as prev." } } sub fifth : Global { my ( $self, $c ) = @_; $c->forward('/first'); } sub end : Private { my ($self, $c) = @_; $c->res->output($c->stash->{message}); } 1;