Index: test/unit/ClientTests.pm =================================================================== --- test/unit/ClientTests.pm (revision 0) +++ test/unit/ClientTests.pm (revision 0) @@ -0,0 +1,140 @@ +use strict; + +package ClientTests; + +# This is woefully incomplete, but it does at least check that +# Client.pm compiles okay. + +use base qw(TWikiTestCase); +BEGIN { + unshift @INC, '../../bin'; + require 'setlib.cfg'; +}; + +use CGI; +use Error qw( :try ); + +use TWiki; +use TWiki::Client; +use TWiki::UI::View; +use TWiki::UI::Edit; + +my $session; +my $twikiRegistrationAgent = 'TWikiRegistrationAgent'; +my ( $joe, $agent, $userTopic ); + +sub new { + my $this = shift()->SUPER::new(@_); + return $this; +} + +sub list_tests { + my $this = shift; + my @set = $this->SUPER::list_tests(); + + my $clz = new Devel::Symdump(qw(ClientTests)); + for my $i ($clz->functions()) { + next unless $i =~ /::verify_/; + foreach my $impl qw( TemplateLogin ApacheLogin NoLogin) { + my $fn = $i; + $fn =~ s/\W/_/g; + my $sfn = 'ClientTests::test_'.$fn.$impl; + no strict 'refs'; + *$sfn = sub { + my $this = shift; + $TWiki::cfg{LoginManager} = 'TWiki::Client::'.$impl; + &$i($this); + }; + use strict 'refs'; + push(@set, $sfn); + } + } + return @set; +} + +sub set_up { + my $this = shift; + $this->SUPER::set_up(); + + $session = new TWiki(); + + $TWiki::cfg{UseClientSessions} = 1; + $TWiki::cfg{PasswordManager} = "TWiki::Users::HtPasswdUser"; + $TWiki::cfg{Htpasswd}{FileName} = "/tmp/htpasswd"; + $TWiki::cfg{AuthScripts} = "edit"; + + # $this->setup_user(); +} + +sub set_up_user { + $joe = $session->{users}->findUser( "joe", "Main.JoeDoe" ); + $agent = $session->{users}->findUser( $twikiRegistrationAgent, + $twikiRegistrationAgent); + $userTopic = + $session->{users}->addUserToTWikiUsersTopic( $joe, $agent); +} + +sub tear_down { + my $this = shift; + $this->SUPER::tear_down(); +} + +sub testClient { + my $this = shift; + + # make sure it compiles, at least. + $this->assert($TWiki::Client::VERSION >= 1.0, "version"); +} + +sub capture { + my $this = shift; + my( $proc, $session ) = @_; + $session->{client}->checkAccess(); + $this->SUPER::capture( @_ ); +} + +sub verify_edit { + my $this = shift; + my ( $query, $text ); + + $query = new CGI ({}); + $query->path_info( "/Main/WebHome" ); + $ENV{SCRIPT_NAME} = "view"; + $session = new TWiki( undef, $query ); + try { + $text = $this->capture( \&TWiki::UI::View::view, $session ); + } catch TWiki::OopsException with { + $this->assert(0,shift->stringify()); + } catch Error::Simple with { + $this->assert(0,shift->stringify()); + }; + + $query = new CGI ({}); + $query->path_info( "/Main/WebHome" ); + $ENV{SCRIPT_NAME} = "edit"; + $session = new TWiki( undef, $query ); + + try { + $text = $this->capture( \&TWiki::UI::Edit::edit, $session ); + } catch TWiki::AccessControlException with { + } catch Error::Simple with { + $this->assert(0,shift->stringify()); + } otherwise { + $this->assert(0, "expected an oops redirect"); + }; + + $query = new CGI ({}); + $query->path_info( "/Main/WebHome" ); + $ENV{SCRIPT_NAME} = "edit"; + $session = new TWiki( "joe", $query ); + + try { + $text = $this->capture( \&TWiki::UI::Edit::edit, $session ); + } catch TWiki::OopsException with { + $this->assert(0,shift->stringify()); + } catch Error::Simple with { + $this->assert(0,shift->stringify()); + }; +} + +1; Index: lib/TWiki.pm =================================================================== --- lib/TWiki.pm (revision 4649) +++ lib/TWiki.pm (working copy) @@ -424,6 +424,7 @@ use TWiki::Access; # access control use TWiki::Attach; # file attachments use TWiki::Attrs; # tag attribute handling +use TWiki::Client; # client session handling use TWiki::Form; # forms use TWiki::Net; # SMTP, get URL use TWiki::Plugins; # plugins handler @@ -626,7 +627,11 @@ $hopts->{'Content-Type'} = $contentType; # New (since 1.026) - $this->{plugins}->modifyHeaderHandler($hopts); + $this->{plugins}->modifyHeaderHandler( $hopts ); + + # add cookie(s) + $this->{client}->modifyHeader( $hopts ); + my $hdr = CGI::header( $hopts ); print $hdr; @@ -660,6 +665,7 @@ if ( $query && $query->param( 'noredirect' )) { my $content = join(' ', @_) . " \n"; $this->writeCompletePage( $query, $content ); + } elsif ( $this->{client}->redirectCgiQuery( $query, $url ) ) { } elsif ( $query ) { print $query->redirect( $url ); } @@ -971,6 +977,7 @@ $this->{search} = new TWiki::Search( $this ); $this->{templates} = new TWiki::Templates( $this ); $this->{attach} = new TWiki::Attach( $this ); + $this->{client} = new TWiki::Client( $this ); # cache CGI information in the session object $this->{cgiQuery} = $query; $this->{remoteUser} = $remoteUser; @@ -1088,12 +1095,18 @@ } else { $this->{urlHost} = $TWiki::cfg{DefaultUrlHost}; } + + # setup the cgi session, from a cookie or the url. this may return + # the login, but even if it does, plugins will get the chance to override + # it below. + my $login = $this->{client}->load(); + # initialize preferences, first part for site and web level $this->{prefs} = new TWiki::Prefs( $this ); # SMELL: there should be a way for the plugin to specify # the WikiName of the user as well as the login. - my $login = $this->{plugins}->load( $TWiki::cfg{DisableAllPlugins} ); + $login = $this->{plugins}->load( $TWiki::cfg{DisableAllPlugins} ) || $login; unless( $login ) { $login = $this->{users}->initializeRemoteUser( $remoteUser ); } @@ -1138,6 +1151,21 @@ =pod +---++ ObjectMethod finish +Complete processing after the client's HTTP request has been responded +to. Right now this only entails one activity: calling TWiki::Client to +flushing the user's +session (if any) to disk. + +=cut + +sub finish { + my $this = shift; + $this->{client}->finish(); +} + +=pod + ---++ ObjectMethod writeLog ( $action, $webTopic, $extra, $user ) * =$action= - what happened, e.g. view, save, rename * =$wbTopic= - what it happened to @@ -2073,7 +2101,7 @@ =pod ----++ StaticMethod registerTagHandler( $fnref ) +---++ StaticMethod registerTagHandler( $tag, $fnref ) STATIC Add a tag handler to the function tag handlers. * =$tag= name of the tag e.g. MYTAG Index: lib/TWiki.cfg =================================================================== --- lib/TWiki.cfg (revision 4649) +++ lib/TWiki.cfg (working copy) @@ -105,8 +105,8 @@ $cfg{RemoteUserFileName} = "$cfg{DataDir}/remoteusers.txt"; # **SELECT TWiki::Users::HtPasswdUser,TWiki::Users::NoPasswdUser** -# TWiki doesn't do its own authentication, but instead works closely -# with the web server that does the authentication. The following +# TWiki can do its own authentication or work closely +# with the web server to do it. The following # settings are used in the TWiki registration scripts to help maintain # users and passwords in the web server's authentication environment. # Name of the password handler implementation. TWiki ships with two @@ -122,6 +122,23 @@ # to your package from here. $cfg{PasswordManager} = 'TWiki::Users::HtPasswdUser'; +# **SELECT TWiki::Client::ApacheLogin,TWiki::Client::TemplateLogin,TWiki::Client::NoLogin** +# TWiki supports different ways of responding when the user asks to log +# in (or is required to log in). They are: +#
  1. +# TWiki::Client::TemplateLogin - Redirect to the login template, which +# asks for a username and password in a form instead of in a browser-specific +# dialog box. This is the same behavior as AuthPagePlugin. +#
  2. +# TWiki::Client::ApacheLogin - Redirect to an '...auth' script for which +# Apache can be configured to ask for authorization information. +#
  3. +# TWiki::Client::NoLogin - Don't support logging in. +#
+# You can provide your own alternative by implementing the authenticate method +# in a new package, and pointing to your package from here. +$cfg{LoginManager} = 'TWiki::Client::NoLogin'; + # **PATH** # Path to the file that stores passwords, for TWiki::Users::HtPasswdUser. # You can use the htpasswd Apache program to create a new @@ -169,6 +186,26 @@ # use TWiki to manually rename the existing topic $cfg{UsersTopicName} = 'TWikiUsers'; +# **BOOLEAN** +# Use persistent CGI session tracking? +$cfg{UseClientSessions} = 1; + +# **STRING 20** +# Name of the "sticky skin" client session variable +$cfg{StickSkinVar} = "stickskin"; + +# **STRING 20** +# Value for StickSkinVar that resets the user's skin preference to +# the TWiki default. +$cfg{StickSkinOffValue} = "default"; + +# **STRING 80** +# Regular expression to describe which scripts require the user to authenticate. +# If you're doing authentication, a good value is "attach, edit, manage, +# passwd, rename, resetpasswd, save, upload, viewauth, rdiffauth". If +# you don't want to require authentication, then leave this blank. +$cfg{AuthScripts} = ""; + # **PATH** # Path control. If set, overrides the default PATH setting to control # where TWiki looks for programs. Check notes for your operating Index: lib/TWiki/UI.pm =================================================================== --- lib/TWiki/UI.pm (revision 4649) +++ lib/TWiki/UI.pm (working copy) @@ -65,7 +65,6 @@ if( $ENV{'GATEWAY_INTERFACE'} ) { # script is called by browser $query = new CGI; - $user = $query->remote_user(); } else { # script is called by cron job or user $scripted = 1; @@ -104,56 +103,21 @@ # end of comment out in production version try { + $session->{client}->checkAccess(); &$method( $session ); } catch TWiki::AccessControlException with { my $e = shift; - # Had an access control violation. See if there is an 'auth' version - # of this script, may be a result of not being logged in. - my $url; - $script =~ s/^(.*\/)([^\/]+)($TWiki::cfg{ScriptSuffix})?$/$1/; - my $scriptPath = $1; - my $scriptName = $2; - $script .= "$scriptPath${scriptName}auth$TWiki::cfg{ScriptSuffix}"; - if( ! $query->remote_user() && -e $script ) { - $url = $ENV{REQUEST_URI}; - if( $url && $url =~ s/\/$scriptName/\/${scriptName}auth/ ) { - # $url i.e. is "twiki/bin/view.cgi/Web/Topic?cms1=val1&cmd2=val2" - $url = $session->{urlHost}.$url; - } else { - # If REQUEST_URI is rewritten and does not contain the script - # name, try looking at the CGI environment variable - # SCRIPT_NAME. - # - # Assemble the new URL using the host, the changed script name, - # the path info, and the query string. All three query - # variables are in the list of the canonical request meta - # variables in CGI 1.1. - $scriptPath = $ENV{'SCRIPT_NAME'}; - my $pathInfo = $ENV{'PATH_INFO'}; - my $queryString = $ENV{'QUERY_STRING'}; - $pathInfo = '/' . $pathInfo if ($pathInfo); - $queryString = '?' . $queryString if ($queryString); - if( $scriptPath && $scriptPath =~ s/\/$scriptName/\/${scriptName}auth/ ) { - $url = $session->{urlHost}.$scriptPath; - } else { - # If SCRIPT_NAME does not contain the script name - # the last hope is to try building up the URL using - # the SCRIPT_FILENAME. - $url = $session->{urlhost}.$session->{scriptUrlPath}.'/'. - ${scriptName}.$TWiki::cfg{ScriptSuffix}; - } - $url .= $pathInfo.$queryString; - } - $session->redirect( $url ); + if( $session->{client}->authenticate() ) { + # okay } else { - $url = $session->getOopsUrl( 'accessdenied', + my $url = $session->getOopsUrl( 'accessdenied', def => 'topic_access', web => $e->{web}, topic => $e->{topic}, params => [ $e->{mode}, $e->{reason} ] ); + $session->redirect( $url ); } - $session->redirect( $url ); } catch TWiki::OopsException with { my $e = shift; @@ -174,6 +138,8 @@ print "Content-type: text/plain\n\n"; print $e->stringify(); }; + + $session->finish(); } =pod twiki Index: lib/TWiki/Client.pm =================================================================== --- lib/TWiki/Client.pm (revision 0) +++ lib/TWiki/Client.pm (revision 0) @@ -0,0 +1,775 @@ +# TWiki Enterprise Collaboration Platform, http://TWiki.org/ +# +# Copyright (C) 2005 Peter Thoeny, peter@thoeny.com +# and TWiki Contributors. All Rights Reserved. TWiki Contributors +# are listed in the AUTHORS file in the root of this distribution. +# NOTE: Please extend that file, not this notice. +# +# Additional copyrights apply to some or all of the code in this +# file as follows: +# Copyright (C) 2000-2003 Andrea Sterbini, a.sterbini@flashnet.it +# Copyright (C) 2005 Garage Games +# Copyright (C) 2005 Crawford Currie http://c-dot.co.uk +# Copyright (C) 2005 Greg Abbas, twiki@abbas.org +# +# 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 2 +# of the License, or (at your option) any later version. For +# more details read LICENSE in the root of this distribution. +# +# 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. +# +# As per the GPL, removal of this notice is prohibited. + +=pod + +---+ package TWiki::Client +This module defines the object that handles Client session +management. It's based heavily on SessionPlugin & AuthPagePlugin. + +=cut + +package TWiki::Client; + +use strict; +use Assert; +use Error qw( :try ); + +# if you want to turn on ip matching, change the "use CGI::Session" +# statement below to "use CGI::Session qw/-ip_match/". +# +# ip matching used to be the recommended approach, but it isn't anymore +# because it has a bunch of drawbacks. specifically: +# * it doesn't help much if clients are behind a NAT firewall +# * it doesn't help much because if a hacker can steal a session id, they +# can steal your password too +# * it breaks clients that get their IP addresses reassigned by DHCP +# * it breaks clients that change their apparent IP address (e.g. VPN) +use CGI::Session; +use CGI::Cookie; + +BEGIN { + # suppress stupid warning in CGI::Cookie + if ( exists $ENV{MOD_PERL} ) { + if ( !defined( $ENV{MOD_PERL_API_VERSION} )) { + $ENV{MOD_PERL_API_VERSION} = 1; + } + } +} + +=pod + +---++ ClassMethod new( $session ) + +Construct new client object. + +=cut + +sub new { + my ( $class, $session ) = @_; + my $this = bless( {}, $class ); + ASSERT($session->isa( 'TWiki')) if DEBUG; + $this->{twiki} = $session; + + $this->{cookies} = []; + @{$this->{authScripts}} = split( /[\s,]+/, $TWiki::cfg{AuthScripts} ); + + return $this; +} + +=pod + +---++ ObjectMethod load() + +Get the client session data, using the cookie and/or the request URL. + +=cut + +sub load { + my $this = shift; + + return unless( $TWiki::cfg{UseClientSessions} ); + + eval "use $TWiki::cfg{LoginManager}"; + throw Error::Simple( 'Login Manager: '.$@) if $@; + + my $query = $this->{twiki}->{cgiQuery}; + + my $twiki = $this->{twiki}; + + # Initialize the session (you may wish to change this directory, + # but /tmp is probably best) + # + # Borrowing from the previous version of TWiki, perhaps using: + # + # TWiki::Func::getDataDir() . '/.session' + # + # would work well for you. Just be sure to create data/.session + # and make it writable by the webserver. + # + # Another experiment might be to change your serializer. Storable + # is a good option. See CGI::Session on http://search.cpan.org/ + # for more information on adding ';initializer:Storable' after the + # 'driver:File' below (other serializers are available as well). + + $this->{haveCookie} = defined($query->raw_cookie( $CGI::Session::NAME )); + + my $cgisession = new CGI::Session( 'driver:File', $query, + { Directory=>'/tmp' } ); + + $this->{cgisession} = $cgisession; + + my $sessionId = $cgisession->id(); + $this->{sessionId} = $sessionId; + + my $guest = $TWiki::cfg{DefaultUserLogin}; + + # For added security, every time a user logs in and gives + # us a user to check, verify that the user we're + # about to flush to the session file is the same as the + # user already stored in the session file. + # + # If there is another valid username stored in the session file, + # then someone has somehow just borrowed a session ID from someone + # else. To prevent further havoc, clear this session ID (perhaps + # in the future it'd be better just to dispatch a new session ID + # to this user; however, if they already have the session ID of + # another user, it's probably best to get rid of it since it has + # been compromised). + # + $TWiki::cfg{LoginManager}->checkSession( $this ); + + # See whether the user was logged in (first webserver, then + # session, then default) + my $authUser = $TWiki::cfg{LoginManager}->getUser( $this ); + $authUser ||= $cgisession->param( 'AUTHUSER' ); + + # if we couldn't get the login manager or the http session to tell + # us who the user is, then let's use the CGI "remote user" + # variable (which may have been set manually by a unit test, + # or it might have come from Apache). + $authUser ||= $twiki->{remoteUser}; # $guest + + #if ( $ENV{'REDIRECT_STATUS'} eq '401' ) { + # # invalidating session due to 401 status + # $cgisession->clear(); + # return 1; + #} + + # Save the user's information again if they do not appear to be a guest + my $sessionIsAuthenticated = ( $authUser ne $guest ); + + my $do_logout = defined( $query ) && $query->param( 'logout' ); + if( $do_logout ) { + $sessionIsAuthenticated = 0; + $authUser = undef; + } + if( ( $do_logout || $sessionIsAuthenticated )) { + $cgisession->param( 'AUTHUSER', $authUser ); + $cgisession->flush(); + } + if( $do_logout ) { + my $origurl = $query->url() . $query->path_info(); + #my $url = $twiki->getScriptUrl( $web, $topic, '' ). + # '?origurl='.$origurl; + $this->redirectCgiQuery( $query, $origurl ); + + # mod_perl is okay with calling exit (it patches it) but + # the unit tests aren't. so doing a "redirect abort" should + # probably terminate processing using an exception. + # exit 0; + } + + # SMELL: $TWiki::cfg{UseTransSessionId} is not set in TWiki.cfg, + # and it isn't clear what it should be set to even if it is. + # $useTransSID sets whether or not to use + # transparent CGI session IDs. If cookies are working, turn + # this off. Otherwise, set it to whatever the user set in + # $useTransSessionId. Still report to the user though that + # %USE_TRANS_SESSIONID% is set to $useTransSessionId + my $useTransSID = (defined($query) && + $query->cookie( $CGI::Session::NAME )) + ? 0 : $TWiki::cfg{UseTransSessionId}; + + # Save our state to member variables, because we'll need them later. + $this->{authUser} = $authUser; + $this->{sessionIsAuthenticated} = $sessionIsAuthenticated; + $this->{useTransSID} = $useTransSID; + + # register tag handlers and values + TWiki::registerTagHandler('SESSIONLOGONURL', \&_SESSIONLOGONURL ); + TWiki::registerTagHandler('SESSIONLOGONURLPATH', \&_SESSIONLOGONURLPATH ); + TWiki::registerTagHandler('LOGIN', \&_LOGIN ); + TWiki::registerTagHandler('LOGOUT',\&_LOGOUT ); + TWiki::registerTagHandler('SESSION_VARIABLE', \&_SESSION_VARIABLE ); + TWiki::registerTagHandler('AUTHENTICATED', \&_AUTHENTICATED ); + + $twiki->{SESSION_TAGS}{SESSIONID} = ( $sessionId || ''); + $twiki->{SESSION_TAGS}{SESSIONVAR} = ( $CGI::Session::NAME || ''); + $twiki->{SESSION_TAGS}{SESSION_IS_AUTHENTICATED} = + ( $sessionIsAuthenticated || ''); + $twiki->{SESSION_TAGS}{STICKSKIN} = + ( defined($query) && $query->param( $TWiki::cfg{StickSkinVar} )) || ''; + $twiki->{SESSION_TAGS}{AUTHUSER_SESSIONVAR} = 'AUTHUSER'; + $twiki->{SESSION_TAGS}{DO_SESSION_IP_MATCHING} = + ( $CGI::Session::IP_MATCH ? 1 : 0 ); + $twiki->{SESSION_TAGS}{USE_TRANS_SESSIONID} = ( $useTransSID || ''); + $twiki->{SESSION_TAGS}{STICKSKINVAR} = + ( $TWiki::cfg{StickSkinVar} || '' ); + $twiki->{SESSION_TAGS}{STICKSKINOFFVALUE} = + ( $TWiki::cfg{StickSkinOffValue} || '' ); + + return $authUser; +} + +=pod + +---++ ObjectMethod checkAccess + +=cut + +sub checkAccess { + + return unless( $TWiki::cfg{UseClientSessions} ); + + my $this = shift; + + if( !$this->{sessionIsAuthenticated} ) { + my $script = $ENV{'SCRIPT_NAME'} || $ENV{'SCRIPT_FILENAME'}; + $script =~ s@^.*/([^/]+)@$1@g; + + if( defined $script) { + my $found = 0; + for (@{$this->{authScripts}}) { + $found = 1 if( $script eq $_ ); + } + + if( $found ) { + my $topic = $this->{twiki}->{topicName}; + my $web = $this->{twiki}->{webName}; + throw TWiki::AccessControlException( + $script, $this->{twiki}->{user}, $web, $topic, + "authorization required"); + } + } + } +} + +=pod + +---++ ObjectMethod finish +Complete processing after the client's HTTP request has been responded +to. Flush the user's session (if any) to disk. + +=cut + +sub finish { + return unless( $TWiki::cfg{UseClientSessions} ); + my $this = shift; + my $cgisession = $this->{cgisession}; + + # this predicate used to be + # $this->{sessionIsAuthenticated} && defined($cgisession), + # but that had the problem that sometimes an unauthenticated version + # of the session would overwrite the more recent authenticated version + # on disk. that's because with mod_perl, an unflushed session object + # would sometimes hang around. then when the apache server was + # terminated, it would get flushed. this way, if we didn't get a + # cookie then we'll tell the session manager that we don't want + # it to _ever_ flush the session. + if($this->{haveCookie}) { + $cgisession->flush(); + } else { + # this is drastic and not really necessary, but unfortunately + # CGI::Session makes it impossible for us to say "don't + # _bother_ writing it to disk if you haven't already". :-( + $cgisession->delete(); + } +} + +=pod + +---++ ObjectMethod userLoggedIn() + +Call this when the user logs in. It's invoked from TWiki::UI::Register::finish +for instance, when the user follows the link in their verification email +message. + +=cut + +sub userLoggedIn { + my ( $this, $authUser, $wikiName ) = @_; + + my $cgisession = $this->{cgisession}; + my $sessionIsAuthenticated = defined($authUser) ? 1 : 0; + + if( $TWiki::cfg{DefaultUserLogin} ne $authUser ) { + $cgisession->param( 'AUTHUSER', $authUser ); + $cgisession->flush(); + } + + $this->{authUser} = $authUser; + $this->{sessionIsAuthenticated} = $sessionIsAuthenticated; +} + +=pod + +---++ ObjectMethod endRenderingHandler() +SMELL: this method uses the plugins endRenderingHandler method which is +deprecated, and stunningly inefficient. It badly needs to be refactored. + +=cut + +sub endRenderingHandler { + return unless( $TWiki::cfg{UseClientSessions} ); + + my $this = shift; + + my $useTransSID = $this->{useTransSID}; + my $sessionId = $this->{sessionId}; + + # This handler is called by getRenderedVersion just after the line loop, that is, + # after almost all XHTML rendering of a topic. tags are removed after this. + + # If cookies are not turned on and transparent CGI session IDs are, + # grab every URL that is an internal link and pass a CGI variable + # with the session ID + if( $useTransSID ) { + # Internal links are specified by forms, hrefs, or onclicks that either + # point to a link with no colons in it or links that match links that + # would bve returned by getScriptUrl. Internal links are additionally + # specified by forms that have no target. + + # Gather the URLs one would expect to be returned by getScriptUrl if a URL + # was inside of quotes (A) or outside of quotes (B) or inside of single quotes + # for javascript (C). + # + # Use these later in all the regex's below. + my $myScriptUrlA = quotemeta($this->{twiki}->getScriptUrl( "ZZZZ", "ZZZZ", "ZZZZ" )); + my $myScriptUrlB = $myScriptUrlA; + my $myScriptUrlC = $myScriptUrlA; + $myScriptUrlA =~ s/ZZZZ/[^"#]*?/g; + $myScriptUrlB =~ s/ZZZZ/[^\\s#>]*?/g; + $myScriptUrlC =~ s/ZZZZ/[^'#>]*?/g; + + # + # NOTE: Lots of the defined's here are to quiet down the highly overrated perl -w + # + + # Catch hyperlinks with targets containing no colon + $_[0] =~ s/(]*?(?<=\s)href=)(?:(")([^:]*?)([#"])|([^:]*?(?=[#\s>])))/@{[ defined($5) ? "$1$5" : "$1$2$3" ]}@{[ ( (defined($3) && ($3=~m!\?!))||(defined($5) && ($5=~m!\?!)) ) ? "&" : "?" ]}$CGI::Session::NAME=$sessionId@{[defined($4) ? "$4" : ""]}/goi; + + # Catch hyperlinks with targets that could be returned by getScriptUrl + $_[0] =~ s/(]*?(?<=\s)href=)(?:(")((?-i:$myScriptUrlA[^"#]*?))([#"])|((?-i:$myScriptUrlB[^\s#>]*?).*?(?=[#\s>])))/@{[ defined($5) ? "$1$5" : "$1$2$3"]}@{[( (defined($3) && ($3=~m!\?!))||(defined($5) && ($5=~m!\?!)) )? "&" : "?" ]}$CGI::Session::NAME=$sessionId@{[ defined($4) ? "$4" : ""]}/goi; + + # Catch onclicks that trigger changes of location.href to targets with no colon + $_[0] =~ s/(<[^>]*?\sonclick=(?:"[^"]*?|)(?=(?:javascript:|))location\.href=)(')([^:]*?)([#'])/$1$2$3@{[ ($3=~m!\?!) ? "&" :"?" ]}$CGI::Session::NAME=$sessionId$4/goi; + + # Catch onclicks that trigger changes of location.href to targets that could be returned by getScriptUrl + $_[0] =~ s/(<[^>]*?\sonclick=(?:"[^"]*?|)(?=(?:javascript:|))location\.href=)(')((?-i:$myScriptUrlC[^'#]*?))([#'])/$1$2$3@{[ ($3=~m!\?!) ? "&" : "?" ]}$CGI::Session::NAME=$sessionId$4/goi; + + + # Catch all FORM elements and add a hidden Session ID variable + # + # Only do this if the form is pointing to an internal link. This occurs if there are no + # colons in its target, if it has no target, or if its target matches a getScriptUrl URL. + # + $_[0] =~ s%(]*?>)%@{ [ "$1" . ( ( $1 =~ /^|\s.*?(?<=\s)action=(?:"(?:[^:]*?|(?-i:$myScriptUrlA))"|(?:[^:"\s]*?|(?-i:$myScriptUrlB))(?:\s|>)))/ ) ? "\n" : "") ] }%gio; + + } + + # And, finally, the logon stuff + # this MUST render after TigerSkinPlugin commonTagsHandler does TIGERLOGON + $_[0] =~ s/%SESSIONLOGON%/$this->_dispLogon()/geo; + $_[0] =~ s/%SKINSELECT%/$this->_skinSelect()/geo; + +} + +=pod + +---++ ObjectMethod modifyHeader( $hdr ) + +=cut + +sub modifyHeader { + return unless( $TWiki::cfg{UseClientSessions} ); + + my( $this, $hopts ) = @_; + + my $query = $this->{twiki}->{cgiQuery}; + + my $c = new CGI::Cookie( -name => $CGI::Session::NAME, + -value => $this->{cgisession}->id, + -path => '/' ); + + my @cs = @{$this->{cookies}}; + push @cs, $c; + $hopts->{cookie} = \@cs; +} + +=pod + +---++ ObjectMethod redirectCgiQuery( $url ) + +=cut + +sub redirectCgiQuery { + return 0 unless $TWiki::cfg{UseClientSessions}; + + my( $this, $query, $url ) = @_; + + my $sessionId = $this->{sessionId}; + my $useTransSID = $this->{useTransSID}; + my $cgisession = $this->{cgisession}; + + if( $useTransSID && $url !~ m/\?$CGI::Session::NAME=/ ) { + # If the URL has no colon in it, it must be an internal URL + if( $url !~ /:/ ) { + # Does it already have CGI parameters passed? + if( $url =~ m/\?/ ) { + my @urlparts = split( $url, /\?/, 2 ); + $url = $urlparts[0] . "?$CGI::Session::NAME=$sessionId&" . $urlparts[1]; + } + # Does it have any anchors passed? + elsif( $url =~ m/#/ ) { + my @urlparts = split( $url, /#/, 2 ); + $url = $urlparts[0] . "?$CGI::Session::NAME=$sessionId#" . $urlparts[1]; + } + # Otherwise, we're the first CGI parameter + else { + $url .= "?$CGI::Session::NAME=$sessionId"; + } + + } else { + # It MAY be an external URL + # This could be better. This could be integrated into the above... + # This could use split instead of regex's... + + # Remember our scriptUrl form to match internal URLs that are referred + # to like external URLs + my $myScriptUrl = quotemeta($this->{twiki}->getScriptUrl( + "XXXX", "YYYY", "ZZZZ" )); + $myScriptUrl =~ s@XXXX@[^/]*?@g; + $myScriptUrl =~ s@YYYY@[^#\?/]*@g; + $myScriptUrl =~ s@ZZZZ@[^/]*?@g; + + # If we start with our internal URL.... + if( $url =~ /(^$myScriptUrl)/o ) { + # Only ask Perl to do that work once; save what we already have + my $theScript = $1; + + # Are there other CGI parameters? + if( $url =~ /(?:^$theScript)(?:\?)(.*)/ ) { + $url = $theScript . '?' . $CGI::Session::NAME . '=' . $sessionId . '&' . $1; + } + # Are there any anchors? + elsif( $url =~ /(?:^$theScript)(#.*)/ ) { + $url = $theScript . '?' . $CGI::Session::NAME . '=' . $sessionId . $1; + } + # Otherwise, we're the first CGI parameter + else { + $url = $theScript . '?' . $CGI::Session::NAME . '=' . $sessionId; + } + } + } + } + + # This usually won't be important, but just in case they haven't + # yet received the cookie and happen to be redirecting, be sure + # they have the cookie. (this is a lot more important with + # transparent CGI session IDs, because the session DIES when those + # people go across a redirect without a ?CGISESSID= in it... But + # EVEN in that case, they should be redirecting to a URL that + # already *HAS* a sessionID in it... Maybe...) + # + # So this is just a big fat precaution, just like the rest of this + # whole handler. + my $cookie = new CGI::Cookie(-name=>$CGI::Session::NAME, + -value=>$cgisession->id, + -path=>'/'); + my @cs = @{$this->{cookies}}; + push @cs, $cookie; + print $query->redirect( -url=>$url, -cookie=>\@cs ); + + return 1; +} + +=pod + +---++ ObjectMethod getSessionValue( $name ) -> $value +Get the value of a session variable. + +=cut + +sub getSessionValue { + my( $this, $key ) = @_; + my $cgisession = $this->{cgisession}; + + return $cgisession->param( $key ); +} + +=pod + +---++ ObjectMethod setSessionValue( $name, $value ) +Set the value of a session variable. +We do not allow setting of AUTHUSER. + +=cut + +sub setSessionValue { + my( $this, $key, $value ) = @_; + my $cgisession = $this->{cgisession}; + + # We do not allow setting of AUTHUSER. + if(( $key ne 'AUTHUSER' ) && + defined( $cgisession->param( $key, $value ))) { + return 1; + } + + return undef; +} + +=pod + +---++ ObjectMethod clearSessionValue( $name ) +Clear the value of a session variable. +We do not allow setting of AUTHUSER. + +=cut + +sub clearSessionValue { + my( $this,, $key ) = @_; + my $cgisession = $this->{cgisession}; + + # We do not allow clearing of AUTHUSER. + if( ( $key ne 'AUTHUSER' ) && + defined( $cgisession->param( $key ))) { + $cgisession->clear( [ $_[1] ] ); + + return 1; + } + + return undef; + +} + +=pod + +---++ ObjectMethod authenticate() + +If the user has an existing authenticated session, the function simply drops +though allowing the calling script to complete. If no auth is in place it +forces redirection to the "login" script, passing it the original URL, +and does not return. + +=cut + +sub authenticate { + my $this = shift; + return $TWiki::cfg{LoginManager}->authenticate( $this ); +} + +=pod + +---++ ObjectMethod sessionLogonUrl() + +=cut + +sub sessionLogonUrl { + my $this = shift; + return $TWiki::cfg{LoginManager}->sessionLogonUrl( $this ); +} + +=pod + +---++ ObjectMethod sessionLogonUrlPath() + +=cut + +sub sessionLogonUrlPath { + my $this = shift; + return $TWiki::cfg{LoginManager}->sessionLogonUrlPath( $this ); +} + +=pod + +---++ ObjectMethod logon( $query, $session ) + +Handler called from the "logon" script. This script is redirected to +if there is no existing session cookie. +If a login name and password have been passed in the query, it +validates these and if authentic, redirects to the original +script. If there is no username in the query or the username/password is +invalid (validate returns non-zero) then it prompts again. + +=cut + +sub logon { + my( $this, $query, $twikiSession ) = @_; + + my $origurl = $query->param( 'origurl' ); + my $loginName = $query->param( 'username' ); + my $loginPass = $query->param( 'password' ); + my $banner = 'You are not logged in'; + my $note = ''; + my $cgisession = $this->{cgisession}; + my $topic = $this->{twiki}->{topicName}; + my $web = $this->{twiki}->{webName}; + + my $currUser = $cgisession->param( 'AUTHUSER' ); + if( $currUser ) { + $banner = $currUser.' is currently logged in'; + $note = 'Enter a new username and password to change identity'; + } + + if( $loginName ) { + my $passwordHandler = $this->{twiki}->{users}->{passwords}; + my $validation = $passwordHandler->checkPassword( $loginName, $loginPass ); + if( $validation ) { + $this->userLoggedIn( $loginName ); + $cgisession->param( 'VALIDATION', $validation ); + $banner = $loginName.' is logged in'; + if( !$origurl || $origurl eq $query->url() ) { + $origurl = $this->{twiki}->getScriptUrl( $web, $topic, 'view' ); + } + $this->redirectCgiQuery( $query, $origurl ); + return; + } else { + $banner = 'Unrecognised user and/or password'; + } + } + + my $tmpl = $this->{twiki}->{templates}->readTemplate( + 'login', $this->{twiki}->getSkin() ); + # TODO: add JavaScript password encryption in the template + # to use a template) + $tmpl =~ s/%ORIGURL%/$origurl/g; + $tmpl =~ s/%BANNER%/$banner/g; + $tmpl =~ s/%NOTE%/$note/g; + + $tmpl = $this->{twiki}->handleCommonTags( $tmpl, $web, $topic ); + $tmpl = $this->{twiki}->{renderer}->getRenderedVersion( $tmpl, '' ); + $this->{twiki}->writePageHeader( $query ); + print $tmpl; +} + +sub _LOGIN { + #($session, \%params, $web, $topic ) = @_; + my $this = shift->{client}; + ASSERT($this->isa('TWiki::Client')); + my $sessionIsAuthenticated = $this->{sessionIsAuthenticated}; + + return '' if($sessionIsAuthenticated); + + my $topic = $this->{twiki}->{topicName}; + my $web = $this->{twiki}->{webName}; + my $url = $this->{twiki}->getScriptUrl( + $web, $topic, "view", ( 'logout' => 1 ) ); + return CGI::a( { href=> "@{ [$this->sessionLogonUrl()] }" }, + 'Log In' ); +} + +sub _LOGOUT { + #($session, \%params, $web, $topic ) = @_; + my $this = shift->{client}; + ASSERT($this->isa('TWiki::Client')); + my $sessionIsAuthenticated = $this->{sessionIsAuthenticated}; + + return "" if(!$sessionIsAuthenticated); + + my $topic = $this->{twiki}->{topicName}; + my $web = $this->{twiki}->{webName}; + my $url = $this->{twiki}->getScriptUrl( + $web, $topic, 'view', ( 'logout' => 1 ) ); + return CGI::a( { href => $url }, 'Log out' ); +} + +sub _AUTHENTICATED { + my( $session, $params ) = @_; + my $this = $session->{client}; + ASSERT($this->isa('TWiki::Client')); + + if( $this->{sessionIsAuthenticated} ) { + return $params->{then} || ''; + } else { + return $params->{else} || ''; + } +} + +sub _SESSION_VARIABLE { + my( $session, $params ) = @_; + my $this = $session->{client}; + ASSERT($this->isa('TWiki::Client')); + my $name = $params->{_DEFAULT}; + + if( defined( $params->{set} ) ) { + $this->setSessionValue( $name, $params->{set} ); + return ''; + } elsif( defined( $params->{clear} )) { + $this->clearSessionValue( $name ); + return ''; + } else { + return $this->getSessionValue( $name ); + } +} + +sub _SESSIONLOGONURL { + my( $session, $params ) = @_; + my $this = $session->{client}; + ASSERT($this->isa('TWiki::Client')); + return $this->sessionLogonUrl(); +} + +sub _SESSIONLOGONURLPATH { + my( $session, $params ) = @_; + my $this = $session->{client}; + ASSERT($this->isa('TWiki::Client')); + return $this->sessionLogonUrlPath(); +} + +sub _dispLogon { + my $this = shift; + + my $topic = $this->{twiki}->{topicName}; + my $web = $this->{twiki}->{webName}; + my $sessionId = $this->{sessionId}; + my $useTransSID = $this->{useTransSID}; + + my $urlToUse = $this->sessionLogonUrlPath(); + + $urlToUse .= ( '?' . $CGI::Session::NAME . '=' . $sessionId ) if $useTransSID; + + my $logon = CGI::a({ class => 'twikiAlert', href => $urlToUse }, + 'Log in' );; + + return $logon; +} + +sub _skinSelect { + my $this = shift; + my $stickSkinVar = $TWiki::cfg{StickSkinVar}; + my $stickSkinOffValue = $TWiki::cfg{StickSkinOffValue}; + + my $skins = $this->{twiki}->{prefs}->getPreferencesValue("SKINS"); + my $skin = $this->{twiki}->getSkin(); + my @skins = split( /,/, $skins ); + unshift @skins, $stickSkinOffValue; + my $options = ''; + foreach my $askin ( @skins ) { + $askin =~ s/\s//go; + my $name = $askin; + $name = '.' if( $name eq $stickSkinOffValue ); + if( $askin eq $skin ) { + $options .= CGI::option( + { selected => 'selected', name => $askin }, $askin ); + } else { + $options .= CGI::option( { name=>$askin }, $askin ); + } + } + return CGI::Select( { name=>$stickSkinVar }, $options ); +} + +1; Index: lib/TWiki/Render.pm =================================================================== --- lib/TWiki/Render.pm (revision 4649) +++ lib/TWiki/Render.pm (working copy) @@ -1189,6 +1189,7 @@ # DEPRECATED plugins hook after PRE re-inserted $plugins->endRenderingHandler( $text ); + $this->{session}->{client}->endRenderingHandler( $text ); # replace verbatim with pre in the final output $this->putBackBlocks( \$text, $removed, Index: lib/TWiki/Func.pm =================================================================== --- lib/TWiki/Func.pm (revision 4649) +++ lib/TWiki/Func.pm (working copy) @@ -64,18 +64,18 @@ ---+++ getSessionValue( $key ) -> $value -Get a session value from the Session Plugin (if installed) +Get a session value from the client session module * =$key= - Session key -Return: =$value= Value associated with key; empty string if not set; undef if session plugin is not installed +Return: =$value= Value associated with key; empty string if not set -*Since:* TWiki::Plugins::VERSION 1.000 (27 Feb 200 +*Since:* TWiki::Plugins::VERSION 1.000 (27 Feb 200) =cut sub getSessionValue { # my( $theKey ) = @_; - return $TWiki::Plugins::SESSION->{plugins}->getSessionValueHandler( @_ ); + return $TWiki::Plugins::SESSION->{client}->getSessionValueHandler( @_ ); } Index: lib/TWiki/Client/TemplateLogin.pm =================================================================== --- lib/TWiki/Client/TemplateLogin.pm (revision 0) +++ lib/TWiki/Client/TemplateLogin.pm (revision 0) @@ -0,0 +1,80 @@ +# TWiki Enterprise Collaboration Platform, http://TWiki.org/ +# +# Copyright (C) 2005 Peter Thoeny, peter@thoeny.com +# and TWiki Contributors. All Rights Reserved. TWiki Contributors +# are listed in the AUTHORS file in the root of this distribution. +# NOTE: Please extend that file, not this notice. +# +# Additional copyrights apply to some or all of the code in this +# file as follows: +# Copyright (C) 2005 Greg Abbas, twiki@abbas.org +# +# 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 2 +# of the License, or (at your option) any later version. For +# more details read LICENSE in the root of this distribution. +# +# 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. +# +# As per the GPL, removal of this notice is prohibited. + +=pod + +---+ package TWiki::Client::TemplateLogin +Redirect to an template-based authentication page. + +=cut + +package TWiki::Client::TemplateLogin; + +use strict; +use Assert; + +sub authenticate { + my( $pkg, $client ) = @_; + + my $session = $client->{session}; + my $query = $session->{cgiQuery}; + my $topic = $session->{topicName}; + my $web = $session->{webName}; + + unless( $session->{client}->{sessionIsAuthenticated} ) { + my $origurl = $query->url() . $query->path_info(); + $origurl =~ s/([^0-9a-zA-Z-_.!*'()\/])/'%'.sprintf('%02x',ord($1))/ge; + my $url = $session->getScriptUrl( $web, $topic, 'login' ). + '?origurl='.$origurl; + $session->redirect( $url ); + # this should use an exception. see the comment in Client.pm. + #exit 0; + } + return 0; +} + +sub sessionLogonUrl { + my ( $pkg, $client ) = @_; + my $session = $client->{session}; + my $topic = $session->{topicName}; + my $web = $session->{webName}; + my $sessionLogonUrl = $session->getScriptUrl( $web, $topic, "login" ); +} + +sub sessionLogonUrlPath { + my ( $pkg, $client ) = @_; + my $path = $pkg->sessionLogonUrl( $client ); + $path =~ s@.*?//.*?/@/@; + return $path; +} + +sub getUser { + # the current user is whoever the session says it is. we don't + # have any other information on that. +} + +sub checkSession { + # it's all good. +} + +1; Index: lib/TWiki/Client/NoLogin.pm =================================================================== --- lib/TWiki/Client/NoLogin.pm (revision 0) +++ lib/TWiki/Client/NoLogin.pm (revision 0) @@ -0,0 +1,57 @@ +# TWiki Enterprise Collaboration Platform, http://TWiki.org/ +# +# Copyright (C) 2005 Peter Thoeny, peter@thoeny.com +# and TWiki Contributors. All Rights Reserved. TWiki Contributors +# are listed in the AUTHORS file in the root of this distribution. +# NOTE: Please extend that file, not this notice. +# +# Additional copyrights apply to some or all of the code in this +# file as follows: +# Copyright (C) 2005 Greg Abbas, twiki@abbas.org +# +# 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 2 +# of the License, or (at your option) any later version. For +# more details read LICENSE in the root of this distribution. +# +# 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. +# +# As per the GPL, removal of this notice is prohibited. + +=pod + +---+ package TWiki::Client::NoLogin + +Don't ask for any usernames or passwords, ever. + +=cut + +package TWiki::Client::NoLogin; + +use strict; +use Assert; + +sub authenticate { + return 0; # cowardly refuse to help. +} + +sub sessionLogonUrl { + # there is no logon link... +} + +sub sessionLogonUrlPath { + # ...there is only Zuul. +} + +sub getUser { + # no user, no worries. +} + +sub checkSession { + # it's all good. +} + +1; Index: lib/TWiki/Client/ApacheLogin.pm =================================================================== --- lib/TWiki/Client/ApacheLogin.pm (revision 0) +++ lib/TWiki/Client/ApacheLogin.pm (revision 0) @@ -0,0 +1,124 @@ +# TWiki Enterprise Collaboration Platform, http://TWiki.org/ +# +# Copyright (C) 2005 Peter Thoeny, peter@thoeny.com +# and TWiki Contributors. All Rights Reserved. TWiki Contributors +# are listed in the AUTHORS file in the root of this distribution. +# NOTE: Please extend that file, not this notice. +# +# Additional copyrights apply to some or all of the code in this +# file as follows: +# Copyright (C) 2005 Greg Abbas, twiki@abbas.org +# +# 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 2 +# of the License, or (at your option) any later version. For +# more details read LICENSE in the root of this distribution. +# +# 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. +# +# As per the GPL, removal of this notice is prohibited. + +=pod + +---+ package TWiki::Client::ApacheLogin +Redirect to a script to ask Apache to ask for a username & password. + +=cut + +package TWiki::Client::ApacheLogin; + +use strict; +use Assert; + +sub authenticate { + my( $pkg, $client ) = @_; + + my $session = $client->{session}; + my $query = $session->{cgiQuery}; + + # Had an access control violation. See if there is an 'auth' version + # of this script, may be a result of not being logged in. + my $script = $ENV{'SCRIPT_FILENAME'}; + $script =~ s/^(.*\/)([^\/]+)($TWiki::cfg{ScriptSuffix})?$/$1/; + my $scriptPath = $1; + my $scriptName = $2; + $script .= "$scriptPath${scriptName}auth$TWiki::cfg{ScriptSuffix}"; + if( ! $query->remote_user() && -e $script ) { + my $url = $ENV{REQUEST_URI}; + if( $url && $url =~ s/\/$scriptName/\/${scriptName}auth/ ) { + # $url i.e. is "twiki/bin/view.cgi/Web/Topic?cms1=val1&cmd2=val2" + $url = $session->{urlHost}.$url; + } else { + # If REQUEST_URI is rewritten and does not contain the script + # name, try looking at the CGI environment variable + # SCRIPT_NAME. + # + # Assemble the new URL using the host, the changed script name, + # the path info, and the query string. All three query + # variables are in the list of the canonical request meta + # variables in CGI 1.1. + $scriptPath = $ENV{'SCRIPT_NAME'}; + my $pathInfo = $ENV{'PATH_INFO'}; + my $queryString = $ENV{'QUERY_STRING'}; + $pathInfo = '/' . $pathInfo if ($pathInfo); + $queryString = '?' . $queryString if ($queryString); + if( $scriptPath && $scriptPath =~ s/\/$scriptName/\/${scriptName}auth/ ) { + $url = $session->{urlHost}.$scriptPath; + } else { + # If SCRIPT_NAME does not contain the script name + # the last hope is to try building up the URL using + # the SCRIPT_FILENAME. + $url = $session->{urlhost}.$session->{scriptUrlPath}.'/'. + ${scriptName}.$TWiki::cfg{ScriptSuffix}; + } + $url .= $pathInfo.$queryString; + } + $session->redirect( $url ); + return 1; + } + + return 0; # can't redirect to an auth script +} + +sub sessionLogonUrl { + my ( $pkg, $client ) = @_; + my $session = $client->{session}; + my $topic = $session->{topicName}; + my $web = $session->{webName}; + my $sessionLogonUrl = $session->getScriptUrl( $web, $topic, "logon" ); +} + +sub sessionLogonUrlPath { + my ( $pkg, $client ) = @_; + my $path = $pkg->sessionLogonUrl( $client ); + $path =~ s@.*?//.*?/@/@; + return $path; +} + +sub getUser { + my ( $pkg, $client ) = @_; + my $query = $client->{session}->{cgiQuery}; + return $query->remote_user() if(defined($query)); + return undef; +} + +sub checkSession { + my ( $pkg, $client ) = @_; + my $cgisession = $client->{cgisession}; + my $query = $client->{session}->{cgiQuery}; + my $authUserSessionVar = $TWiki::Client::authUserSessionVar; + + $cgisession->clear() if( + defined($cgisession) && defined($cgisession->param) && + defined($query) && defined( $query->remote_user() ) && + defined($authUserSessionVar) && + defined( $cgisession->param( $authUserSessionVar ) ) && + "" ne $query->remote_user() && + "" ne $cgisession->param( $authUserSessionVar ) && + $query->remote_user() ne $cgisession->param( $authUserSessionVar ) ); +} + +1; Index: lib/TWiki/Users/HtPasswdUser.pm =================================================================== --- lib/TWiki/Users/HtPasswdUser.pm (revision 4649) +++ lib/TWiki/Users/HtPasswdUser.pm (working copy) @@ -267,7 +267,8 @@ $this->{error} = undef; - return 1 if( $encryptedPassword eq $this->fetchPass( $user ) ); + my $pw = $this->fetchPass( $user ); + return 1 if( defined($pw) && ($encryptedPassword eq $pw) ); $this->{error} = 'Invalid user/password'; return 0; Index: lib/TWiki/Users.pm =================================================================== --- lib/TWiki/Users.pm (revision 4649) +++ lib/TWiki/Users.pm (working copy) @@ -54,7 +54,7 @@ $this->{session} = $session; - my $impl = $TWiki::cfg{PasswordManager}; + my $impl = $TWiki::cfg{PasswordManager}; eval "use $impl"; die "Password Manager: $@" if $@; $this->{passwords} = $impl->new( $session ); @@ -246,7 +246,7 @@ my $today = TWiki::Time::formatTime(time(), '$day $mon $year', 'gmtime'); # add to the cache - $this->{U2W}{$user->login()} = $user->{web} . "." , $user->wikiName(); + $this->{U2W}{$user->login()} = $user->{web} . "." . $user->wikiName(); # add name alphabetically to list foreach my $line ( split( /\r?\n/, $text) ) { Index: lib/TWiki/UI/Register.pm =================================================================== --- lib/TWiki/UI/Register.pm (revision 4649) +++ lib/TWiki/UI/Register.pm (working copy) @@ -655,7 +655,7 @@ my $topic = $session->{topicName}; my $web = $session->{webName}; - # $this->{session}->writeLog('verifyuser', $loginName, "$userName"); + # $session->writeLog('verifyuser', $loginName, "$userName"); _emailRegistrationConfirmations( $session, $data ); } @@ -710,6 +710,11 @@ $data->{WikiName}, $data->{LoginName} ); + # let the client session know that we're logged in. (This probably + # eliminates the need for the registrationHandler call above, + # but we'll leave them both in here for now.) + $session->{client}->userLoggedIn( $data->{LoginName}, $data->{WikiName} ); + # add user to TWikiUsers topic my $user = $session->{users}->findUser( $data->{LoginName}, $data->{WikiName} ); Index: lib/TWiki/UI/Edit.pm =================================================================== --- lib/TWiki/UI/Edit.pm (revision 4649) +++ lib/TWiki/UI/Edit.pm (working copy) @@ -98,6 +98,10 @@ my $extra = ''; my $topicExists = $store->topicExists( $webName, $topic ); + # If you want to edit, you have to be able to view and change. + TWiki::UI::checkAccess( $session, $webName, $topic, 'view', $user ); + TWiki::UI::checkAccess( $session, $webName, $topic, 'change', $user ); + # Check lease, unless we have been instructed to ignore it my $breakLock = $query->param( 'breaklock' ) || ''; unless( $breakLock ) { @@ -155,12 +159,6 @@ $store->readTopic( undef, $webName, $topic, undef ); } - # If you want to edit, you have to be able to view and change. - TWiki::UI::checkAccess( $session, $webName, $topic, - 'view', $session->{user} ); - TWiki::UI::checkAccess( $session, $webName, $topic, - 'change', $session->{user} ); - if( $saveCmd && ! $session->{user}->isAdmin()) { throw TWiki::OopsException( 'accessdenied', def=>'only_group', web => $webName, topic => $topic, Index: lib/TWiki/UI/View.pm =================================================================== --- lib/TWiki/UI/View.pm (revision 4649) +++ lib/TWiki/UI/View.pm (working copy) @@ -92,6 +92,8 @@ if( $topicExists ) { ( $currMeta, $currText ) = $store->readTopic ( $session->{user}, $webName, $topicName, undef ); + TWiki::UI::checkAccess( $session, $webName, $topicName, + 'view', $session->{user}, $currText ); ( $revdate, $revuser, $showRev ) = $currMeta->getRevisionInfo(); $revdate = TWiki::Time::formatTime( $revdate ); Index: bin/configure =================================================================== --- bin/configure (revision 4649) +++ bin/configure (working copy) @@ -118,6 +118,8 @@ 'POSIX' => "I18N (core module) and Security", 'Digest::MD5' => "MD5 encoded passwords", 'Text::Diff' => 'UpgradeTWiki', + 'CGI::Cookie' => "sessions", + 'CGI::Session' => "sessions", }; my $I18Mods = @@ -869,6 +871,38 @@ return $mess; }; +sub _CHECKVAR_UseClientSessions { + if( $TWiki::cfg{UseClientSessions} ) { + if (!eval "use CGI::Cookie; 1") { + return ERROR(" + The CGI::Cookie Perl module is required for session support, but is not + available. Please either install it or turn UseClientSessions off. + "); + } + if (!eval "use CGI::Session; 1") { + return ERROR(" + The CGI::Session Perl module is required for session support, but is not + available. Please either install it or turn UseClientSessions off. + "); + } + } + return ''; +} + +sub _CHECKVAR_AuthScripts { + if( $TWiki::cfg{AuthScripts} ) { + if( $TWiki::cfg{LoginManager} eq "TWiki::Client::NoLogin" ) { + return WARN(" +You've asked that some scripts require authentication, but haven't +specified a way for users to log in. Please pick a LoginManager +(above) other than NoLogin. + "); + } + } + return ''; +} + + # Generates the appropriate HTML for getting a value to configure the # entry. The opts are additional parameters, and by convention may # be a number (for a string length), a comma separated list of values Index: data/TWiki/TWikiPreferences.txt =================================================================== --- data/TWiki/TWikiPreferences.txt (revision 4649) +++ data/TWiki/TWikiPreferences.txt (working copy) @@ -80,6 +80,9 @@ $comment +---++ Session settings + * Standard login message + * Set LOGON_OR_WELCOME = %SESSION_IF_AUTHENTICATED% *[[TWiki.WelcomeGuest][Welcome]]* %WIKIUSERNAME% %SESSION_ELSE% [[TWiki.TWikiRegistration][Register]] or %SESSIONLOGON% %SESSION_ENDIF% ---++ TWiki Platform Settings