This is the third article in a series to describe how I am
going to fit TWiki's
DakarRelease into an intranet portal. See
HowToAvoidRegistration and
TransparentAuthenticationRevisited
for more explanations. I also count
Dakar's Item 594
as related, though I'm afraid that I'm only adding to the confusion...
Registration On Demand
This is only useful if you always have $ENV{REMOTE_USER} set,
for example because the whole TWiki directory - or the whole server -
require users to authenticate before they even view a single page.
This is a hack and not (yet) a contrib. A proof of concept for
HowToAvoidRegistration. I've called it RegistrationOnDemandHack, because it
actually doesn't
avoid registration at all - it just postpones it until I
think that TWiki ought to ask for it.
Nevertheless I am likely to deploy it in my own TWiki, so I'd be very grateful
if further
Dakar releases would allow the patch to be
applied without too much changes before I (or someone else) comes up with
something which could be called a Contrib. ;->
The basic idea is, as outlined in
HowToAvoidRegistration, to separate between
the "authentication" and "authorization". The hack doesn't require any significant
code changes in the TWiki core, just some additions, and it abuses unpublished
interfaces.
Design outline
The 'authenticated' context in TWiki's session object is silently being interpreted as
'
registered and authenticated' (or
authorized and authenticated, to bring it to
the terminology of Anton Aylward).
In a TWiki driven by
TemplateLogin
, this is the normal flow: You need to
register first to
get a user id suitable for
authentication. But usually, authentication comes first in
every session. Only after you have verified the client's identity he is granted
appropriate access rights.
Given an external authentication managenent,
Twiki draws the wrong conclusion about the context. It calls the session
'authenticated' just because it finds
$ENV{REMOTE_USER}, regardless of whether
the user ever registered. But we still
need a registration to achieve the same TWiki
status as with
TemplateLogin
.
Therefore I have prepared a login manager which makes sure that TWiki's 'authenticated'
context is entered only when an authenticated user has registered himself before as well.
This login manager does two things:
- If, during client session setup, we find that the external user given
$ENV{REMOTE_USER} is unknown in the TWikiUsers topic, then pretend that this is a login from 'guest' (or whatever is configured as $cfg{DefaultUserLogin}).
- If TWiki encounters an action which needs authentication (triggered by
UI.pm to catch the TWiki::AccessControlException), our forceAuthentication routine knows that we have an authentication, but lack the TWikiRegistration. So we are just doing that on-the-fly.
The second step is similar to TemplateLogin in that it requests the data needed on-the-fly, and after
successful operation returns to whatever action had been interrupted. We can
even re-use it's
bin/login stub to call us back without change.
On the other hand, the actions performed by the second step are those of
the usual
TWikiRegistration process:
It creates a user's home page and adds him to the
TWikiUsers list.
The new logon Manager lib/TWiki/Client/RegistrationOnDemandLogin.pm
I've derived the code for this login manager from the existing managers,
e.g.
lib/TWiki/Client/TemplateLogin.pm.
The calling structure is:
-
bin/* : A TWiki request calls TWiki::UI::run
-
TWiki/UI.pm: A new session object is created there using TWiki::new
-
TWiki.pm: After a bunch of initializations which I am gladly skipping here, the variable $login is established by loading a client session:
my $login = $this->{client}->loadSession();
-
TWiki/Client.pm: The login user is being evaluated:
# See whether the user was logged in (first webserver, then
= =# session, then default)
= =my $authUser = $this->getUser( $this );
= The routine =getUser is supposed to be provided by the login manager. (I am skipping a couple of ||= statements)
# Save the users information again if they do not appear to be a guest
my $sessionIsAuthenticated =
( $authUser && $authUser ne $TWiki::cfg{DefaultUserLogin} );
Ahaaaa! So all we need to do is to make sure that $authUser is returned as $TWiki::cfg{DefaultUserLogin} by my login manager to make the session unauthenticated.
-
UI.pm checks whether the action is permitted, and if it isn't and the session is considered 'unauthenticated' then the login manager's forceAuthentication method is called in the block catching the AccessControlException.
- There we just print out a registration form, recording the original URL.
- The user fills the form. This is an unauthenticated procedure, so I'll skip the details. We are directly dropping into the login mangers
login procedure.
- This routine does an on-the-fly registration and then redirects to the original url. All is starting over again, but...
- If checkAccess is still catching an AccessControlException,
- forceAuthentication returns failure (
undef) because now the session is considered 'authenticated'.
How to activate
You shouldn't. You
really shouldn't. It isn't tested sufficiently, it is just
my personal proof of concept. But maybe you'd like to have a look at the code?
This is waaaay less dangerous.
- Unzip the attachment into your TWiki source tree
- Either manually add
$TWiki::cfg{LoginManager} = 'TWiki::Client::RegistrationOnDemandLogin'; to your lib/LocalSite.cfg or apply the one-line patch to lib/TWiki.cfg and activate RegistrationOnDemandLogin with bin/configure
Todo
- Create a test suite
- Fix bugs and documentation
- Refactor the common code parts from =lib/TWiki/UI/Register.pm. I am abusing no less than four unpublished interfaces:
-
_getDataFromQuery( $query );
-
_validateRegistration ( $twiki, $data, $query, $topic );
-
_newUserFromTemplate($twiki, 'NewUserTemplate', $data);
-
_emailRegistrationConfirmations( $twiki, $data );
- Refactor the common template parts from TWikiRegistration and
templates/registerform.tmpl
- Keep an eye on Item 594
on the Dakar bugs list.
- Allow the form fields in
templates/registerform.tmpl to be pre-loaded (e.g. from LDAP data)
--
HaraldJoerg - 09 Oct 2005
I agree, it is entirely silly that TWiki assumes my login name is my REMOTE_USER name.. My entire site is Apache Authentication protected, but my twiki is to be separately protected using sessions.
--
PeterPayne - 14 Feb 2006
Actually, all I had to do was comment out the line:
$authUser ||= $twiki->{remoteUser};
in Client.pm (sub loadSession).
Now it always asks for my login even though I've got Apache Basic authentication on my site.
--
PeterPayne - 14 Feb 2006
If I understand that correctly, then your TWiki
needs to do its own user management, in addition to what's in place on your site. Doesn't that mean that TWiki users have to login twice: Once to use the site (their browser will ask for that), and once to use TWiki? Do you have different persons using the same user id for Apache authentication but want to differentiate between them in TWiki? If so, then your solution seems to be a reasonable fix - for what I'd call a broken user management at the site level. Wait - additionally there's the benefit that your TWiki users can "log out", which is impossible with Apache's basic authentication without closing the browser.
What I have in mind (and what I'm using at my site) is to
use the sitewide apache authentication for TWiki's user management, by automagically registering the users. This allows my TWiki, and my TWiki users, to get rid of an own password management. But of course this needs an 1:1 relationship between TWiki users and
$ENV{REMOTE_USER}.
--
HaraldJoerg - 14 Feb 2006
Bug: If a user registers with the same Wiki name as someone else, then the Hack overwrites the old entry in the User List. Therefore, if there are two Joe Smiths -- one whose LDAP username is jsmith and the other whose LDAP username is jpsmith -- and jsmith is already registered, if then jpsmith later registers but forgets that there is another Joe Smith and therefore does not use his middle initial but sets his Wiki name as JoeSmith, the first Joe Smith loses his registration. The default TWiki Registration script, however, forbids registering with a Wiki name that is already in use.
--
AndrewBanks - 07 Jun 2006
Hmmmm; let's make one thing perfectly clear.
This is not a hack. Deriving a new login manager is
recommended practice, and I am
delighted to see that someone has taken up the challenge! if you need help packaging it as a contrib (which somehow i doubt!) then just shout.
--
CrawfordCurrie - 07 Jun 2006
Crawford - it depends on what qualifies something as a hack. A diff file required for installation may be ok, but using a couple of private routines in core modules is not. I have been surprised when Andrew told me per mail that the attached file (from October 2005, that's Dakar beta phase) would still "work" in 4.0.2ish, only to find out that it definitely will fail in 4.0.3. One of the private routines in
lib/TWiki/UI/Register.pm which I've been using has vanished.
So the "contrib" has survived the beta testing phase, one or two Dakar releases, to be killed by the upcoming bugfix release. Tough luck.
The user registration in TWiki4 is still carrying the "under construction" sign with it. A
svn diff for
Register.pm between 4.0.2 and svn HEAD has 648 lines, after 100 lines between 4.0.1 and 4.0.2. I don't dare to have another shot at either of RegistrationOnDemandHack or
ApprovingRegistrations before this calms down.
At least some of the changes would IMHO have been better postponed to a 4.1 release, with a previous collection and discussion of requirements. But
RegistrationBeyondDakar is still as pristine as it was in November last year, and feedback to
RegistrationAsPluginRequirements wasn't overwhelming....
--
HaraldJoerg - 07 Jun 2006
Of course. The login manager API is reasonably well defined, and I naively assumed you had stayed within the bounds of that API. I was frankly a bit surprised you had been able to; now I realise you weren't
Yes, registration has fluxed too much. It's a shame that the registration changes were in place around the middle of last year, but it has taken this long to get the test feedback. Moving too quickly to pluggable registration fills me with fear, because we will go through a similar requirements cycle.
BTW a better way to do registration on the fly is to call
TWiki::UI::register_cgi, as is done by the tests. For example:
sub registerUser {
my ($login, $fn, $sn, $email ) = @_;
my $query = new CGI ({
'TopicName' => [ 'TWikiRegistration' ],
'Twk1Email' => [ $email ],
'Twk1WikiName' => [ $fn.$sn ],
'Twk1Name' => [ $fn.' '.$sn ],
'Twk0Comment' => [ '' ],
'Twk1LoginName' => [ $login ],
'Twk1FirstName' => [ $fn ],
'Twk1LastName' => [ $sn ],
'action' => [ 'register' ]
});
my $regoff = $TWiki::cfg{Register}{NeedVerification};
$TWiki::cfg{Register}{NeedVerification} = 0;
$query->path_info( "/$TWiki::cfg{UsersWebName}/TWikiRegistration" );
my $session = new TWiki( $TWiki::cfg{DefaultUserName}, $query);
try {
TWiki::UI::Register::register_cgi($session);
} catch TWiki::OopsException with {
# This should be the response on successful registration. ignore it
} catch Error::Simple with {
# Do what you like with other errors
} always {
$TWiki::cfg{Register}{NeedVerification} = $regoff;
}
}
This code should work on 4.0.0 through 4.0.3.
--
CrawfordCurrie - 08 Jun 2006
Crawford: Yes, this would have been a sensible approach - only that it breaks a function which I had desired, and which, as it seems, is the reason behind the bug found by Andrew.
I had desired to make registration "on-the-fly" so that, after successful registration, the user is redirected back to wherever he had been before he was asked to register (there's an extra hidden parameter
origurl for that, which is missing in
Register.pm). The conventional registration process ends in an oops page which is somewhat misleading in this context.
Unfortunately
_validateRegistration in
Register.pm checks for the existence of the user topic in the web
passed by the form (as
PATH_INFO ) instead of the user web - so my hack fails if the user has been in a web different from Main when being asked to register.
I'd guess the following one-line-patch would fix the bug:
$ diff -U2 registerform.tmpl~ registerform.tmpl
--- registerform.tmpl~ 2005-10-10 00:40:58.000000000 +0200
+++ registerform.tmpl 2006-06-08 23:56:22.000000000 +0200
@@ -16,5 +16,5 @@
%TMPL:P{"simpleheader"}%
---+ %BANNER%
-<form action='%SCRIPTURL%/login%SCRIPTSUFFIX%/%WEB%/%TOPIC%' method='post'>
+<form action='%SCRIPTURL%/login%SCRIPTSUFFIX%/%MAINWEB%/%HOMETOPIC%' method='post'>
<table border='0'>
<td align="right"> First Name: </td>
After successful registration a user now ends up in Main.WebHome, which may be confusing - but since 4.0.3 is ante portas, I can't convince myself for a better solution.
One last comment on Crawford's "Moving too quickly to pluggable registration": I never wanted to move
quickly. That's why I never pressed to include any of my registration changes into Dakar, and instead wrote some topics about collecting the requirements for Edinburgh. Registration should be done with due care, given the difference between intranets (LDAP available, external user management) and internets, where the first priority is fighting SPAM.
--
HaraldJoerg - 08 Jun 2006
Argh. There are so many topics floating around on this I just don't know where to go. Makes me wish there were a mailing list :-).
I have a similar need to many here: I have an intranet TWiki installation where authentication is done via a proprietary Apache plugin that sets
REMOTE_USER to the user's corporate ID (a number) before even the first page of the Wiki is ever displayed. So, users are
always authenticated and can never "log out". The plugin manages special cookies that expire after so many hours etc. etc. TWiki should not need to care about
any of that stuff.
We don't want people to be known on the site by their corporate ID, of course, nor do we want to have to use that for authorization, so we still asked people to register and create a
WikiName, which was added to
TWikiUsers and their own personal Wiki page was created. We have a corporate LDAP database which I queried (using the corporate ID from
REMOTE_USER to obtain things like the email address, etc.)
In Cairo, I had hacked up the
register script to do all the above and it was working quite well.
In Dakar, I'm pretty lost unfortunately. This is what I would like:
- Authentication is assumed if
REMOTE_USER is set (which it always will be for me).
- Mapping is done between the login value (the corporate ID from
REMOTE_USER) and the Wiki name--I presume taken from TWikiUsers.
I
seem to have the above working, more or less. If I have an entry in
TWikiUsers then it's used, otherwise the corporate ID is used. The rest of it eludes me however:
- I don't want any "Log In" options to be displayed in the menu bar.
- I only want a "Register" option to be displayed if the user hasn't already registered (doesn't have any entry in TWikiUsers yet)
- I'm OK with having the user be forced to click a link to register: the "automatically register" capability would be nice but it's not that important to me.
- In the registration screen, I don't want to display the login name at all.
- I don't want to ask for or set any sort of password during registration.
- I don't want to do the verification email thing.
- I want to obtain the email address from LDAP. In my Cairo implementation I put this in a hidden field so the user didn't even have a chance to change it; this would be nice but is not essential.
- There are other pieces of information such as phone number, etc. that I was obtaining from LDAP and putting into the user's new Wiki page: this is nice but optional.
I've done a lot of Perl programming but I don't know TWiki's internals, esp. Dakar. I've created myself a new TWiki::Client::FooLogin class which is currently just a copy of the
ApacheLogin class. I've looked at the code in the
RegistrationOnDemandLogin class, but I'm scared off because it apparently doesn't work in TWiki 4.0.3 and above (I'm running TWiki 4.0.4 with hotfix 2).
In configure I have
UseClientSessions off,
MapUserToWikiName on,
PasswordManager none,
NeedVerification off, and
AllowLoginName off (according to the docs this seems like the wrong value, but it does work).
Does anyone have any hints, or pointers to documentation I've missed (I've read the pages I've found on the TWiki site, including
InterfacingToExternalAccessControlListManagers,
CairoDakarRegistrationDifference,
TransparentAuthenticationRevisited,
TWikiUserAuthentication of course, and the
LoginNameAliasesPlugin.
I'm happy to share my new Client module, if I can get it working...
--
PaulSmith - 31 Jul 2006
Paul,
On our internal TWiki we are using a NTLM based apache authentication, which results in $ENV{REMOTE_USER} being set. I re-worked the code here to do just about everything you've asked for.
It auto-magically fills out the needed forms from the information in the authorization cookie (our mechanism, rather than LDAP). The user is not given the option to change anything, they merely click on Register.
I am working on getting this functioning in version 4.0.4 (some Register.pm calls changed) (I have it working in 4.0.1). I will give more of an update, once I get it fully working (early next week).
--
CraigMeyer - 14 Oct 2006
Ok,
I've gotten this working in 4.0.4. I used Register::_registerSingleBulkUser() to do the work from Client::RegistrationOnDemandLogin(). registerSingle needed some work to get everything done in the proper order (correct in Register::finish()). Now, I think I should have just worked with finish()

It uses a template register.pattern.tmpl.
Paul,
Would you put the LDAP lookups in the PERL code, or use the LDAP plugin?
--
CraigMeyer - 16 Oct 2006
I have posted a tar-ball which contains the following files:
- Updated lib/TWiki/UI/Register.pm
- The Diffs for lib/TWiki/UI/Register_pm.diffs
- lib/TWiki/Client/RegistrationOnDemandLogin.pm
- templates/register.pattern.tmpl
You should unpack this in it's OWN directory, and then copy over the files. Warning: It will overwrite whatever files are in your twiki tree!
I made modifications to TWiki::UI::Register.pm in order to make _registerSingleBulkUser() work properly.
In lib/LocalSite.cfg, I set the
LoginManager to be:
$TWiki::cfg{LoginManager} = 'TWiki::Client::RegistrationOnDemandLogin';
I know this is a bit sketchy, If you have questions/problems please let me know.
--
CraigMeyer - 14 Nov 2006
In lib/TWiki/Client/RegistrationOnDemandLogin.pm, sub _register_on_demand(), The code depends on the PERL environment variable
$ENV{REMOTE_USER} to be setup, by the .htaccess apache authentication. Our corporate intranet authentication also sets up two other environment variables
BixToken_name (User's fullname) and
BixToken_mail (User's internal email). For you to re-use this code, these need to be changed to get the values from your authentication (or LDAP server).
--
CraigMeyer - 14 Nov 2006
Hi,
I've tried the hack but even if I'm already registered with a valid username/wikiname my requeste has been forwarded to the registration form in anycase.
Do you kindly have any suggestion?
--
AntenoreGatta - 12 Jan 2007
Check
TWikiUsers and make sure the fields: Wikiname AND REMOTE_USER are both listed and correct.
Make sure your browser is accepting cookies.
Make sure the apache .htaccess Authentication is working.
--
CraigMeyer - 15 Jan 2007
If you need support please ask questions in the
Support web.
--
PeterThoeny - 16 Jan 2007
Just got this working again in V4.1 and I still needed to make modifications to UI/Register.pm _registerSingleBulkUser(). I may be doing this incorrectly

I needed to re-order the code, so that _createUserTopic() was called, before the findUser(). And needed to get the
TwikiRegistrationAgent in order to successfully add the user with addUserToMapping(), otherwise didn't have the privilege.
--
CraigMeyer - 25 Jan 2007