From ad1be93dffb2b25223fb93cbe2a7d349c6b5c127 Mon Sep 17 00:00:00 2001 From: Paul Seamons Date: Thu, 18 Oct 2007 00:00:00 +0000 Subject: [PATCH] CGI::Ex 2.21 --- Changes | 7 ++++ META.yml | 2 +- lib/CGI/Ex.pm | 2 +- lib/CGI/Ex/App.pm | 2 +- lib/CGI/Ex/Auth.pm | 74 ++++++++++++++++++++++++++---------------- lib/CGI/Ex/Conf.pm | 2 +- lib/CGI/Ex/Die.pm | 2 +- lib/CGI/Ex/Dump.pm | 2 +- lib/CGI/Ex/Fill.pm | 2 +- lib/CGI/Ex/JSONDump.pm | 2 +- lib/CGI/Ex/Template.pm | 2 +- lib/CGI/Ex/Validate.pm | 2 +- 12 files changed, 63 insertions(+), 38 deletions(-) diff --git a/Changes b/Changes index f1577ae..72d2875 100644 --- a/Changes +++ b/Changes @@ -1,3 +1,10 @@ +2.21 + 2007-10-18 + * Add logout_hook to Auth + * Remove key_payload + * Only accept payload from cram styles + * Added disable_simple_cram to enforce protecting server generated payloads + 2.20 2007-10-08 * Added success_hook and failure_hook to Auth (not documented) diff --git a/META.yml b/META.yml index 8a005ad..1d67b91 100644 --- a/META.yml +++ b/META.yml @@ -1,6 +1,6 @@ --- #YAML:1.0 name: CGI-Ex -version: 2.20 +version: 2.21 abstract: CGI utility suite - makes powerful application writing fun and easy license: ~ generated_by: ExtUtils::MakeMaker version 6.36 diff --git a/lib/CGI/Ex.pm b/lib/CGI/Ex.pm index faa5a36..bf4bc02 100644 --- a/lib/CGI/Ex.pm +++ b/lib/CGI/Ex.pm @@ -24,7 +24,7 @@ use vars qw($VERSION use base qw(Exporter); BEGIN { - $VERSION = '2.20'; + $VERSION = '2.21'; $PREFERRED_CGI_MODULE ||= 'CGI'; @EXPORT = (); @EXPORT_OK = qw(get_form diff --git a/lib/CGI/Ex/App.pm b/lib/CGI/Ex/App.pm index 5e78cc3..4c93364 100644 --- a/lib/CGI/Ex/App.pm +++ b/lib/CGI/Ex/App.pm @@ -13,7 +13,7 @@ BEGIN { eval { use Scalar::Util }; } -our $VERSION = '2.20'; +our $VERSION = '2.21'; sub new { my $class = shift || croak "Usage: ".__PACKAGE__."->new"; diff --git a/lib/CGI/Ex/Auth.pm b/lib/CGI/Ex/Auth.pm index a762ead..2dd895d 100644 --- a/lib/CGI/Ex/Auth.pm +++ b/lib/CGI/Ex/Auth.pm @@ -18,7 +18,7 @@ use MIME::Base64 qw(encode_base64 decode_base64); use Digest::MD5 qw(md5_hex); use CGI::Ex; -$VERSION = '2.20'; +$VERSION = '2.21'; ###----------------------------------------------------------------### @@ -49,6 +49,8 @@ sub get_valid_auth { local $self->{'no_cookie_verify'} = 1; $self->check_valid_auth; # verify the logout so we can capture the username if possible + $self->logout_hook; + if ($self->bounce_on_logout) { my $key_c = $self->key_cookie; $self->delete_cookie({key => $key_c}) if $self->cookies->{$key_c}; @@ -88,7 +90,6 @@ sub get_valid_auth { user => delete $hash->{$key}, test_pass => delete $hash->{ $self->key_pass }, expires_min => delete($hash->{ $self->key_save }) ? -1 : delete($hash->{ $self->key_expires_min }) || $self->expires_min, - payload => delete $hash->{ $self->key_payload } || '', }, from => 'form', }) || next; @@ -99,12 +100,13 @@ sub get_valid_auth { } ### generate a fresh cookie if they submitted info on plaintext types - if ($self->use_plaintext || ($data->{'type'} && $data->{'type'} eq 'crypt')) { + if ($is_form + && ($self->use_plaintext || ($data->{'type'} && $data->{'type'} eq 'crypt'))) { $self->set_cookie({ key => $self->key_cookie, val => $self->generate_token($data), no_expires => ($data->{ $self->key_save } ? 0 : 1), # make it a session cookie unless they ask for saving - }) if $is_form; # only set the cookie if we found info in the form - the cookie will be a session cookie after that + }); ### always generate a cookie on types that have expiration } else { @@ -160,6 +162,14 @@ sub success_hook { return; } +sub logout_hook { + my $self = shift; + if (my $meth = $self->{'logout_hook'}) { + return $meth->($self); + } + return; +} + sub handle_failure { my $self = shift; my $args = shift || {}; @@ -287,7 +297,6 @@ sub key_expires_min { shift->{'key_expires_min'} ||= 'cea_expires_min' } sub form_name { shift->{'form_name'} ||= 'cea_form' } sub key_verify { shift->{'key_verify'} ||= 'cea_verify' } sub key_redirect { shift->{'key_redirect'} ||= 'cea_redirect' } -sub key_payload { shift->{'key_payload'} ||= 'cea_payload' } sub key_loggedout { shift->{'key_loggedout'} ||= 'loggedout' } sub bounce_on_logout { shift->{'bounce_on_logout'} ||= 0 } sub secure_hash_keys { shift->{'secure_hash_keys'} ||= [] } @@ -299,6 +308,7 @@ sub use_plaintext { my $s = shift; $s->use_crypt || ($s->{'use_plaintext'} || sub use_base64 { my $s = shift; $s->{'use_base64'} = 1 if ! defined $s->{'use_base64'}; $s->{'use_base64'} } sub expires_min { my $s = shift; $s->{'expires_min'} = 6 * 60 if ! defined $s->{'expires_min'}; $s->{'expires_min'} } sub failed_sleep { shift->{'failed_sleep'} ||= 0 } +sub disable_simple_cram { shift->{'disable_simple_cram'} } sub logout_redirect { my ($self, $user) = @_; @@ -371,17 +381,14 @@ sub login_hash_common { key_time => $self->key_time, key_save => $self->key_save, key_expires_min => $self->key_expires_min, - key_payload => $self->key_payload, key_redirect => $self->key_redirect, form_name => $self->form_name, script_name => $self->script_name, path_info => $self->path_info, md5_js_path => $self->js_uri_path ."/CGI/Ex/md5.js", - use_plaintext => $self->use_plaintext, $self->key_user => $data->{'user'} || '', $self->key_pass => '', # don't allow for this to get filled into the form $self->key_time => $self->server_time, - $self->key_payload => $self->generate_payload({%$data, login_form => 1}), $self->key_expires_min => $self->expires_min, text_user => $self->text_user, text_pass => $self->text_pass, @@ -541,9 +548,10 @@ sub verify_password { } } - ### looks like a normal cram + ### looks like a simple_cram } elsif ($data->{'cram_time'}) { - $data->add_data(type => 'cram'); + $data->add_data(type => 'simple_cram'); + die "Type simple_cram disabled during verify_password" if $self->disable_simple_cram; my $real = $pass =~ /^[a-f0-9]{32}$/ ? lc($pass) : md5_hex($pass); my $str = join("/", @{$data}{qw(user cram_time expires_min payload)}); my $sum = md5_hex($str .'/'. $real); @@ -595,7 +603,7 @@ sub generate_token { my $pass = defined($data->{'test_pass'}) ? $data->{'test_pass'} : $data->{'real_pass'}; $token = $data->{'user'} .'/'. $pass; - ### all other types go to cram - secure_hash_cram, cram, plaintext and md5 + ### all other types go to cram - secure_hash_cram, simple_cram, plaintext and md5 } else { my $user = $data->{'user'} || die "Missing user"; my $real = defined($data->{'real_pass'}) ? ($data->{'real_pass'} =~ /^[a-f0-9]{32}$/ ? lc($data->{'real_pass'}) : md5_hex($data->{'real_pass'})) @@ -606,7 +614,7 @@ sub generate_token { die "User can not contain a \"/\." if $user =~ m|/|; my $array; - if (! $data->{'prefer_cram'} + if (! $data->{'prefer_simple_cram'} && ($array = eval { $self->secure_hash_keys }) && @$array) { my $rand1 = int(rand @$array); @@ -615,6 +623,7 @@ sub generate_token { my $sum = md5_hex($str .'/'. $real .('/sh.'.$array->[$rand1].'.'.$rand2)); $token = $str .'/'. $sum . '/sh.'.$rand1.'.'.$rand2; } else { + die "Type simple_cram disabled during generate_token" if $self->disable_simple_cram; my $str = join("/", $user, $self->server_time, $exp, $load); my $sum = md5_hex($str .'/'. $real); $token = $str .'/'. $sum; @@ -737,7 +746,6 @@ sub login_form { [% error %]
- @@ -774,8 +782,10 @@ sub hide_save { my $self = shift; return defined($self->{'hide_save'}) ? $se sub text_submit { my $self = shift; return defined($self->{'text_submit'}) ? $self->{'text_submit'} : 'Login' } sub login_script { - return shift->{'login_script'} || q { - [%~ IF ! use_plaintext %] + my $self = shift; + return $self->{'login_script'} if $self->{'login_script'}; + return '' if $self->use_plaintext || $self->disable_simple_cram; + return q { @@ -787,9 +797,8 @@ sub login_script { var p = f.[% key_pass %].value; var t = f.[% key_time %].value; var s = f.[% key_save %] && f.[% key_save %].checked ? -1 : f.[% key_expires_min %].value; - var l = f.[% key_payload %].value; - var str = u+'/'+t+'/'+s+'/'+l; + var str = u+'/'+t+'/'+s+'/'+''; var sum = document.md5_hex(str +'/' + document.md5_hex(p)); var f2 = document.[% form_name %]_jspost; @@ -800,7 +809,6 @@ sub login_script { return false; } - [% END ~%] }; } @@ -887,7 +895,7 @@ CGI::Ex::Auth allows for auto-expiring, safe and easy web based logins. Auth us javascript modules that perform MD5 hashing to cram the password on the client side before passing them through the internet. -For the stored cookie you can choose to use cram mechanisms, +For the stored cookie you can choose to use simple cram mechanisms, secure hash cram tokens, auto expiring logins (not cookie based), and Crypt::Blowfish protection. You can also choose to keep passwords plaintext and to use perl's crypt for testing @@ -915,11 +923,11 @@ or may be passed as properties to the new constuctor such as in the following: get_pass_by_user => \&my_pass_sub, key_user => 'my_user', key_pass => 'my_pass', - login_template => \"", + login_header => \"

My Login

", }); The following methods will look for properties of the same name. Each of these will be -defined separately. +described separately. cgix cleanup_user @@ -933,7 +941,6 @@ defined separately. key_expires_min key_logout key_pass - key_payload key_redirect key_save key_time @@ -950,6 +957,7 @@ defined separately. handle_failure success_hook failure_hook + logout_hook no_cookie_verify path_info script_name @@ -992,9 +1000,10 @@ Possible arguments are: types. payload - a payload that will be passed to generate_payload and then will be added to cram type tokens. It cannot contain a /. - prefer_cram - If the secure_hash_keys method returns keys, and it is a non-plaintext + prefer_simple_cram + - If the secure_hash_keys method returns keys, and it is a non-plaintext token, generate_token will create a secure_hash_cram. Set - this value to true to tell it to use a normal cram. This + this value to true to tell it to use a simple_cram. This is generally only useful in testing. The following are types of tokens that can be generated by generate_token. Each type includes @@ -1019,7 +1028,7 @@ pseudocode and a sample of a generated that token. of the password but the get_pass_by_user hook returns the crypt'ed password, the token will not be able to be verified. - cram: + simple_cram: user := "paul" real_pass := "123qwe" server_time := 1148512991 # a time in seconds since epoch @@ -1104,17 +1113,14 @@ Passed to the template swapped during login_print. key_pass # $self->key_pass, # the password fieldname key_time # $self->key_time, # the server time field name key_save # $self->key_save, # the save password checkbox field name - key_payload # $self->key_payload, # the payload fieldname key_redirect # $self->key_redirect, # the redirect fieldname form_name # $self->form_name, # the name of the form script_name # $self->script_name, # where the server will post back to path_info # $self->path_info, # $ENV{PATH_INFO} if any md5_js_path # $self->js_uri_path ."/CGI/Ex/md5.js", # script for cramming - use_plaintext # $self->use_plaintext, # used to avoid cramming $self->key_user # $data->{'user'}, # the username (if any) $self->key_pass # '', # intentional blankout $self->key_time # $self->server_time, # the server's time - $self->key_payload # $data->{'payload'} # the payload (if any) $self->key_expires_min # $self->expires_min # how many minutes crams are valid text_user # $self->text_user # template text Username: text_pass # $self->text_pass # template text Password: @@ -1317,6 +1323,18 @@ The text items shown in the default login template. The default values are: text_pass "Password:" text_save "Save Password ?" +=item C + +Disables simple cram type from being an available type. Default is +false. If set, then one of use_plaintext, use_crypt, or +secure_hash_keys should be set. Setting this option allows for +payloads to be generated by the server only - otherwise a user who +understands the algorithm could generate a valid simple_cram cookie +with a custom payload. + +Another option would be to only accept payloads from tokens if use_blowfish +is set and armor was equal to "blowfish." + =back =head1 LICENSE diff --git a/lib/CGI/Ex/Conf.pm b/lib/CGI/Ex/Conf.pm index 0b13ca8..f635f20 100644 --- a/lib/CGI/Ex/Conf.pm +++ b/lib/CGI/Ex/Conf.pm @@ -29,7 +29,7 @@ use vars qw($VERSION ); @EXPORT_OK = qw(conf_read conf_write in_cache); -$VERSION = '2.20'; +$VERSION = '2.21'; $DEFAULT_EXT = 'conf'; diff --git a/lib/CGI/Ex/Die.pm b/lib/CGI/Ex/Die.pm index a87f500..4f3aa72 100644 --- a/lib/CGI/Ex/Die.pm +++ b/lib/CGI/Ex/Die.pm @@ -23,7 +23,7 @@ use CGI::Ex; use CGI::Ex::Dump qw(debug ctrace dex_html); BEGIN { - $VERSION = '2.20'; + $VERSION = '2.21'; $SHOW_TRACE = 0 if ! defined $SHOW_TRACE; $IGNORE_EVAL = 0 if ! defined $IGNORE_EVAL; $EXTENDED_ERRORS = 1 if ! defined $EXTENDED_ERRORS; diff --git a/lib/CGI/Ex/Dump.pm b/lib/CGI/Ex/Dump.pm index d83d6c2..796b83e 100644 --- a/lib/CGI/Ex/Dump.pm +++ b/lib/CGI/Ex/Dump.pm @@ -17,7 +17,7 @@ use vars qw(@ISA @EXPORT @EXPORT_OK $VERSION use strict; use Exporter; -$VERSION = '2.20'; +$VERSION = '2.21'; @ISA = qw(Exporter); @EXPORT = qw(dex dex_warn dex_text dex_html ctrace dex_trace); @EXPORT_OK = qw(dex dex_warn dex_text dex_html ctrace dex_trace debug); diff --git a/lib/CGI/Ex/Fill.pm b/lib/CGI/Ex/Fill.pm index c4ab9f4..cbf38de 100644 --- a/lib/CGI/Ex/Fill.pm +++ b/lib/CGI/Ex/Fill.pm @@ -24,7 +24,7 @@ use vars qw($VERSION use base qw(Exporter); BEGIN { - $VERSION = '2.20'; + $VERSION = '2.21'; @EXPORT = qw(form_fill); @EXPORT_OK = qw(fill form_fill html_escape get_tagval_by_key swap_tagval_by_key); }; diff --git a/lib/CGI/Ex/JSONDump.pm b/lib/CGI/Ex/JSONDump.pm index f212ec7..0c2852c 100644 --- a/lib/CGI/Ex/JSONDump.pm +++ b/lib/CGI/Ex/JSONDump.pm @@ -17,7 +17,7 @@ use strict; use base qw(Exporter); BEGIN { - $VERSION = '2.20'; + $VERSION = '2.21'; @EXPORT = qw(JSONDump); @EXPORT_OK = @EXPORT; diff --git a/lib/CGI/Ex/Template.pm b/lib/CGI/Ex/Template.pm index 715dc19..57ffa0e 100644 --- a/lib/CGI/Ex/Template.pm +++ b/lib/CGI/Ex/Template.pm @@ -25,7 +25,7 @@ use vars qw($VERSION $VOBJS ); -$VERSION = '2.20'; +$VERSION = '2.21'; ### install true symbol table aliases that can be localized *QR_PRIVATE = *Template::Alloy::QR_PRIVATE; diff --git a/lib/CGI/Ex/Validate.pm b/lib/CGI/Ex/Validate.pm index 18a3501..1948872 100644 --- a/lib/CGI/Ex/Validate.pm +++ b/lib/CGI/Ex/Validate.pm @@ -22,7 +22,7 @@ use vars qw($VERSION @UNSUPPORTED_BROWSERS ); -$VERSION = '2.20'; +$VERSION = '2.21'; $DEFAULT_EXT = 'val'; $QR_EXTRA = qr/^(\w+_error|as_(array|string|hash)_\w+|no_\w+)/; -- 2.43.0