aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Changes4
-rw-r--r--META.yml31
-rw-r--r--Makefile.PL25
-rw-r--r--README1
-rw-r--r--catdap.yml125
-rw-r--r--lib/CatDap.pm91
-rw-r--r--lib/CatDap/Controller/Root.pm69
-rw-r--r--lib/CatDap/Controller/register.pm150
-rw-r--r--lib/CatDap/Controller/user.pm335
-rw-r--r--lib/CatDap/I18N/af.po119
-rw-r--r--lib/CatDap/I18N/fr.po133
-rw-r--r--lib/CatDap/I18N/messages.pot129
-rw-r--r--lib/CatDap/Model/Proxy.pm40
-rw-r--r--lib/CatDap/Model/User.pm40
-rw-r--r--lib/CatDap/View/Email.pm33
-rw-r--r--lib/CatDap/View/TT.pm36
-rw-r--r--root/favicon.icobin0 -> 2551 bytes
-rw-r--r--root/index.tt34
-rw-r--r--root/register/check.tt4
-rw-r--r--root/register/complete.tt16
-rw-r--r--root/register/index.tt45
-rw-r--r--root/static/images/btn_120x50_built.pngbin0 -> 3826 bytes
-rw-r--r--root/static/images/btn_120x50_built_shadow.pngbin0 -> 3681 bytes
-rw-r--r--root/static/images/btn_120x50_powered.pngbin0 -> 3862 bytes
-rw-r--r--root/static/images/btn_120x50_powered_shadow.pngbin0 -> 3673 bytes
-rw-r--r--root/static/images/btn_88x31_built.pngbin0 -> 2517 bytes
-rw-r--r--root/static/images/btn_88x31_built_shadow.pngbin0 -> 2274 bytes
-rw-r--r--root/static/images/btn_88x31_powered.pngbin0 -> 2542 bytes
-rw-r--r--root/static/images/btn_88x31_powered_shadow.pngbin0 -> 2304 bytes
-rw-r--r--root/static/images/catalyst_logo.pngbin0 -> 13710 bytes
-rwxr-xr-xscript/catdap_cgi.pl30
-rwxr-xr-xscript/catdap_create.pl57
-rwxr-xr-xscript/catdap_fastcgi.pl47
-rwxr-xr-xscript/catdap_i18n.sh16
-rwxr-xr-xscript/catdap_server.pl60
-rwxr-xr-xscript/catdap_test.pl40
-rw-r--r--t/01app.t10
-rw-r--r--t/02pod.t10
-rw-r--r--t/03podcoverage.t10
-rw-r--r--t/controller_register.t9
-rw-r--r--t/controller_user.t9
-rw-r--r--t/model_Proxy.t6
-rw-r--r--t/model_User.t6
-rw-r--r--t/view_Email.t7
-rw-r--r--t/view_TT.t7
45 files changed, 1784 insertions, 0 deletions
diff --git a/Changes b/Changes
new file mode 100644
index 0000000..c7443e8
--- /dev/null
+++ b/Changes
@@ -0,0 +1,4 @@
+This file documents the revision history for Perl extension CatDap.
+
+0.01 2010-10-11 09:11:10
+ - initial revision, generated by Catalyst
diff --git a/META.yml b/META.yml
new file mode 100644
index 0000000..13c53f4
--- /dev/null
+++ b/META.yml
@@ -0,0 +1,31 @@
+---
+abstract: 'Catalyst based application'
+author:
+ - 'Buchan Milne'
+build_requires:
+ ExtUtils::MakeMaker: 6.42
+ Test::More: 0.88
+configure_requires:
+ ExtUtils::MakeMaker: 6.42
+distribution_type: module
+generated_by: 'Module::Install version 0.95'
+license: perl
+meta-spec:
+ url: http://module-build.sourceforge.net/META-spec-v1.4.html
+ version: 1.4
+name: CatDap
+no_index:
+ directory:
+ - inc
+ - t
+requires:
+ Catalyst::Action::RenderView: 0
+ Catalyst::Plugin::ConfigLoader: 0
+ Catalyst::Plugin::Static::Simple: 0
+ Catalyst::Runtime: 5.80022
+ Config::General: 0
+ Moose: 0
+ namespace::autoclean: 0
+resources:
+ license: http://dev.perl.org/licenses/
+version: 0.01
diff --git a/Makefile.PL b/Makefile.PL
new file mode 100644
index 0000000..7a4561c
--- /dev/null
+++ b/Makefile.PL
@@ -0,0 +1,25 @@
+#!/usr/bin/env perl
+# IMPORTANT: if you delete this file your app will not work as
+# expected. You have been warned.
+use inc::Module::Install;
+use Module::Install::Catalyst; # Complain loudly if you don't have
+ # Catalyst::Devel installed or haven't said
+ # 'make dist' to create a standalone tarball.
+
+name 'CatDap';
+all_from 'lib/CatDap.pm';
+
+requires 'Catalyst::Runtime' => '5.80022';
+requires 'Catalyst::Plugin::ConfigLoader';
+requires 'Catalyst::Plugin::Static::Simple';
+requires 'Catalyst::Action::RenderView';
+requires 'Moose';
+requires 'namespace::autoclean';
+requires 'Config::General'; # This should reflect the config file format you've chosen
+ # See Catalyst::Plugin::ConfigLoader for supported formats
+test_requires 'Test::More' => '0.88';
+catalyst;
+
+install_script glob('script/*.pl');
+auto_install;
+WriteAll;
diff --git a/README b/README
new file mode 100644
index 0000000..6fcdf85
--- /dev/null
+++ b/README
@@ -0,0 +1 @@
+Run script/catdap_server.pl to test the application.
diff --git a/catdap.yml b/catdap.yml
new file mode 100644
index 0000000..81e214f
--- /dev/null
+++ b/catdap.yml
@@ -0,0 +1,125 @@
+# This is the default configuration for CatDap. You should not need to
+# modify it, unless you actually want to fix some default behaviour
+# that is configured below
+#
+# For site-specific configuration, copy this file (catdap.yml) to
+# have a _local suffix (catdap_local.yml) and make your changes there.
+# Note that you only need to keep configuration sections that differ,
+# the rest will be inherited
+
+name: CatDap
+default_view: TT
+
+Model::Proxy:
+ base: ou=People,dc=mageia,dc=org
+ dn: cn=catdap,ou=System Accounts,dc=mageai,dc=org
+ password: FIXME
+ host: ldap.mageia.org
+ start_tls: 1
+
+# dn and password should not be required here, we rebind with credentials
+# from the authenticated user using Model::LDAP::FromAuthentication
+Model::User:
+ base: ou=People,dc=mageia,dc=org
+ host: ldap.mageia.org
+ start_tls: 1
+
+authentication:
+ default_realm: ldap
+ realms:
+ ldap:
+ credential:
+ class: Password
+ password_field: password
+ password_type: self_check
+ store:
+ class: LDAP
+ ldap_server: 'ldap.mageia.org'
+ start_tls: 1
+ binddn: cn=catdap,ou=System Accounts,dc=mageai,dc=org
+ bindpw: FIXME
+ user_basedn: "ou=people,dc=mageia,dc=org"
+ user_filter: '(&(objectClass=inetOrgPerson)(uid=%s))'
+ user_scope: 'one'
+ user_field: 'uid'
+ use_roles: 1
+ role_basedn: 'ou=group,dc=mageia,dc=org'
+ role_scope: 'one'
+ role_field: 'cn'
+ role_value: 'uid'
+
+Controller::User:
+# Attributes that the user can edit. Attributes present but not listed here
+# will be show (if not in skip_attrs), but the form will not allow editing.
+# Note that the actual access contols should be implemented on the LDAP side,
+# that is where they belong, or you are being inconsistent if users have other
+# means to access LDAP
+ editable_attrs:
+ - cn
+ - sn
+ - givenName
+ - mail
+ - mobile
+ - roomNumber
+ - secretary
+ - mailForwardingAddress
+
+# Currently not used, we only respect editable_attrs
+ uneditable_attrs:
+ - uid
+ # - uidNumber
+ # - gidNumber
+ - homeDirectory
+ - host
+ - manager
+ - krb5PrincipalName
+# List of attributes which are not displayed at all in the user view
+ skip_attrs:
+ - objectClass
+ - krb5Key
+ - sambaMungedDial
+ - sambaPasswordHistory
+ - userPassword
+ - sambaLMPassword
+ - sambaNTPassword
+ - sambaPwdMustChange
+ - sambaSID
+ - sambaPrimaryGroupSID
+ - sambaAcctFlags
+ - sambaPwdCanChange
+ - sambaPwdLastSet
+ - sambaKickOffTime
+ - sambaUserWorkstations
+ - sambaLogonTime
+ - krb5KeyVersionNumber
+ - krb5PasswordEnd
+ - krb5MaxLife
+ - krb5MaxRenew
+ - krb5KDCFlags
+ - shadowLastChange
+ - shadowWarning
+ - shadowMax
+ - shadowMin
+ - shadowInactive
+ - shadowExpire
+ - shadowFlag
+
+Plugin::Captcha:
+ new:
+ gd_font: giant
+ width: 100
+ height: 40
+ lines: 7
+
+ create:
+ - normal
+ - rect
+
+ particle:
+ - 100
+
+ gd_font: giant
+
+Plugin::Session:
+ expires: 600
+
diff --git a/lib/CatDap.pm b/lib/CatDap.pm
new file mode 100644
index 0000000..d9a37a9
--- /dev/null
+++ b/lib/CatDap.pm
@@ -0,0 +1,91 @@
+package CatDap;
+use Moose;
+use namespace::autoclean;
+
+use Catalyst::Runtime 5.80;
+
+# Set flags and add plugins for the application
+#
+# -Debug: activates the debug mode for very useful log messages
+# ConfigLoader: will load the configuration from a Config::General file in the
+# application's home directory
+# Static::Simple: will serve static files from the application's root
+# directory
+
+use Catalyst qw/
+ -Debug
+ ConfigLoader
+ Static::Simple
+ Session
+ Session::State::Cookie
+ Session::Store::File
+ Captcha
+ Authentication
+ I18N
+/;
+
+extends 'Catalyst';
+
+our $VERSION = '0.01';
+$VERSION = eval $VERSION;
+
+# Configure the application.
+#
+# Note that settings in catdap.conf (or other external
+# configuration file that you set up manually) take precedence
+# over this when using ConfigLoader. Thus configuration
+# details given here can function as a default configuration,
+# with an external configuration file acting as an override for
+# local deployment.
+
+__PACKAGE__->config(
+ name => 'CatDap',
+ # Disable deprecated behavior needed by old applications
+ disable_component_resolution_regex_fallback => 1,
+);
+
+sub begin : Private {
+ my ( $self, $c ) = @_;
+
+ my $locale = $c->request->param('locale');
+ $c->response->headers->push_header( 'Vary' => 'Accept-Language' ); # hmm vary and param?
+ $c->languages( $locale ? [ $locale ] : undef );
+}
+
+
+#c->languages(['af']);
+
+# Start the application
+__PACKAGE__->setup();
+
+
+=head1 NAME
+
+CatDap - Catalyst based application
+
+=head1 SYNOPSIS
+
+ script/catdap_server.pl
+
+=head1 DESCRIPTION
+
+[enter your description here]
+
+=head1 SEE ALSO
+
+L<CatDap::Controller::Root>, L<Catalyst>
+
+=head1 AUTHOR
+
+Buchan Milne
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+sub end : ActionClass('RenderView') {}
+
+1;
diff --git a/lib/CatDap/Controller/Root.pm b/lib/CatDap/Controller/Root.pm
new file mode 100644
index 0000000..550cd61
--- /dev/null
+++ b/lib/CatDap/Controller/Root.pm
@@ -0,0 +1,69 @@
+package CatDap::Controller::Root;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller' }
+
+#
+# Sets the actions in this controller to be registered with no prefix
+# so they function identically to actions created in MyApp.pm
+#
+__PACKAGE__->config(namespace => '');
+
+=head1 NAME
+
+CatDap::Controller::Root - Root Controller for CatDap
+
+=head1 DESCRIPTION
+
+[enter your description here]
+
+=head1 METHODS
+
+=head2 index
+
+The root page (/)
+
+=cut
+
+sub index :Path :Args(0) {
+ my ( $self, $c ) = @_;
+
+ # Hello World
+ #$c->response->body( $c->welcome_message );
+}
+
+=head2 default
+
+Standard 404 error page
+
+=cut
+
+sub default :Path {
+ my ( $self, $c ) = @_;
+ $c->response->body( 'Page not found' );
+ $c->response->status(404);
+}
+
+=head2 end
+
+Attempt to render a view, if needed.
+
+=cut
+
+sub end : ActionClass('RenderView') {}
+
+=head1 AUTHOR
+
+Buchan Milne
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
diff --git a/lib/CatDap/Controller/register.pm b/lib/CatDap/Controller/register.pm
new file mode 100644
index 0000000..d8fc7c1
--- /dev/null
+++ b/lib/CatDap/Controller/register.pm
@@ -0,0 +1,150 @@
+package CatDap::Controller::register;
+use Moose;
+use namespace::autoclean;
+use Data::Dumper;
+use Email::Valid;
+use Data::UUID;
+
+BEGIN {extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+CatDap::Controller::register - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) : Form {
+ my ( $self, $c ) = @_;
+
+ #my $form = Catalyst::Controller::HTML::FormFu->form();
+ #$c->response->body('Matched CatDap::Controller::register in register.');
+}
+
+sub check : Local {
+ my ( $self, $c ) = @_;
+
+ my %details = %{$c->request->params};
+ my $username = $c->request->params->{uid};
+ $username =~ s/[A-Z]/[a-z]/g;
+ my @errors;
+ $c->stash(errors => []);
+ # Check username, start with letter, followed by letters or numbers
+ if ($username !~ /^[a-z][a-z0-9_\-]*$/) {
+ push @errors, $c->loc('Invalid username');
+ }
+ my $email = $c->request->params->{mail1};
+ if (! Email::Valid->address($email)) {
+ push @errors, $c->loc('Invalid email address');
+ }
+ if ($email ne $c->request->params->{mail2}) {
+ push @errors, $c->loc('Addresses do not match');
+ }
+ if (! $c->validate_captcha($c->req->param('validate'))){
+ push @errors, $c->loc('Incorrect validation text, please try again');
+ }
+ if ($c->request->params->{gn} !~ /^\p{IsAlnum}*\z$/) {
+ push @errors, $c->loc('The first name supplied contains illegal characters');
+ }
+ if ($c->request->params->{sn} !~ /^\p{IsAlnum}*\z$/) {
+ #push @errors, $c->loc('The') . ' ' $c->loc('surname') . ' ' . $c->loc('supplied contains unprintable characters');
+ push @errors, $c->loc('The surname supplied contains illegal characters');
+ }
+
+ if (@errors gt 0) {
+ $c->stash(errors => \@errors);
+ $c->stash(template => 'register/index.tt');
+ } else {
+ # check in LDAP now that we have validated username and email
+ my $mesg = $c->model('Proxy')->search("(mail=$email)");
+ if ($mesg->entries ne 0) {
+ push @errors,$c->loc('An account already exists with this email address');
+ }
+ $mesg = $c->model('Proxy')->search("(uid=$username)");
+ if ($mesg->entries ne 0) {
+ my $foo = Dumper(${$c->config}{'Model::Proxy'}{'base'});
+ push @errors,$c->loc('An account already exists with this username');
+ #push @errors,"under base" . __PACKAGE__->config{Model::Proxy}{base};
+ push @errors,$foo;
+ }
+ if (@errors gt 0) {
+ $c->stash(errors => \@errors);
+ $c->stash(template => 'register/index.tt');
+ } else {
+ my $dn = "uid=$username,${$c->config}{'Model::Proxy'}{'base'}";
+ my $ug = Data::UUID->new;
+ my $password = $ug->create_str();
+ my $cn = $c->request->params->{gn} . " " . $c->request->params->{sn};
+ $mesg = $c->model('Proxy')->add($dn,
+ attr => [
+ objectclass => [ 'inetOrgPerson' ],
+ sn => $c->request->params->{sn},
+ gn => $c->request->params->{gn},
+ cn => $cn,
+ mail => $email,
+ pwdReset => 'TRUE',
+ userPassword => $password,
+ ]
+ );
+ if ($mesg) {
+ push @errors,$mesg->error;
+ $c->stash(errors => \@errors);
+ #$c->stash(template => 'register/index.tt');
+ }
+ #} else {
+ my $body;
+ $body .= $c->loc('Dear') . " $c->request->params->{gn},\n";
+ $body .= $c->loc("Your Mageia indentity has been successfully created, but requires activation.\n");
+ $body .= $c->loc("To activate your account, please follow the link below.\n");
+ $body .= $c->uri_for('/user/firstlogin') . "?username=$username&key=$password";
+ $c->stash->{email} = {
+ to => $email,
+ from => 'no-reply@mageia.org',
+ subject => $c->loc('Mageia Identity Activation'),
+ body => $body,
+ };
+
+ $c->forward( $c->view('Email') );
+ if ( scalar( @{ $c->error } ) ) {
+ my $errors = join "\n",@{ $c->error };
+ $c->response->body($c->loc('An error occured sending the email, but your account was created. Please try the password recovery process f you entered the correct email address: [_1]', $errors));
+ $c->error(0); # Reset the error condition if you need to
+ }
+ $c->stash(template => 'register/complete.tt');
+ $c->stash(message => 'Check your email');
+ #}
+ }
+ }
+}
+
+sub captcha : Local {
+ my ($self, $c) = @_;
+ return $c->create_captcha();
+}
+
+
+
+=head1 AUTHOR
+
+Buchan Milne
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
diff --git a/lib/CatDap/Controller/user.pm b/lib/CatDap/Controller/user.pm
new file mode 100644
index 0000000..41fe412
--- /dev/null
+++ b/lib/CatDap/Controller/user.pm
@@ -0,0 +1,335 @@
+package CatDap::Controller::user;
+use Moose;
+use namespace::autoclean;
+use Net::LDAP;
+use Net::LDAP::Schema;
+use Net::LDAP::Extension::SetPassword;
+use Net::LDAP::Control::PasswordPolicy 0.02;
+use Crypt::CBC;
+use Data::Dumper;
+
+BEGIN {extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+CatDap::Controller::user - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+=head2 auto
+
+Ensure the user is logged in. In order to bind as the user, we use
+CatDap::Model::User, which uses Catalyst::Model::LDAP::FromAuthentication,
+which effectively requires calling $c->authenticate on every request.
+
+To do this, we keep the password, encrypted with blowfish, using the
+(for now), first 3 octets of IPv4 request address and the session id as the key.
+
+So, if the user does "not exist", we authenticate them, if it succeeds we encrypt
+the password and store it in the session.
+
+If the user is logged in, we get the encrypted password from the session, decrypt
+it (we need to handle failure to decrypt it better)
+
+=cut
+
+sub auto : Private {
+ my ( $self, $c ) = @_;
+ my $cipher;
+ my $password;
+ my $mesg;
+ my $dn;
+ my $keyprefix = sprintf("%02x%02x%02x",split /\./,$c->req->address);
+ $c->log->info("Using $keyprefix as first part of enc key");
+ if (! defined $c->user) {
+ $c->log->info("No session, logging user in");
+ if (! $c->authenticate({ username => $c->req->param('username'),
+ password => $c->req->param('password') || $c->req->param('key')}) ) {
+ #TODO: ppolicy ....
+ $c->stash(errors => ['Incorrect username or password']);
+ $c->stash(template => 'index.tt');
+ $c->forward('/index');
+ $c->detach;
+ } else {
+ #if (defined $c->user->pwdReset) {
+ # $c->res->redirect('/user');
+ #}
+ #$c->persist_user;
+ $c->log->info('Logging user in to LDAP');
+ $cipher = Crypt::CBC->new( -key => $keyprefix . $c->sessionid,
+ -cipher => 'Blowfish'
+ ) or die $!;
+ $c->session->{enc_password} = $cipher->encrypt($c->req->param('password') || $c->req->param('key'));
+ $c->session->{dn} = $c->user->ldap_entry->dn;
+ $c->session->{user} = $c->req->param('username');
+ $password = $c->req->param('password') || $c->req->param('key');
+ return 1;
+ }
+
+ } else {
+ $cipher = Crypt::CBC->new( -key => $keyprefix . $c->sessionid,
+ -cipher => 'Blowfish'
+ ) or die $!;
+ $password = $cipher->decrypt($c->session->{enc_password});
+ $c->log->info("Re-authenticating user " . $c->session->{user});
+ $c->authenticate({username => $c->session->{user},password => $password});
+ $c->log->info($@) if $@;
+ return 1;
+ }
+
+}
+
+=head2 index
+
+=cut
+
+sub index :Path :Args(0) {
+ my ( $self, $c ) = @_;
+ my $cipher;
+ my $password;
+ my $mesg;
+ my $dn;
+
+ if (not defined $c->user ) {
+ $c->stash(template => 'index.tt');
+ $c->forward('/index');
+ $c->detach;
+ }
+ my $schemaldap = Net::LDAP->new(${$c->config}{'Model::Proxy'}{'host'}) or warn "LDAP bind failed: $!";
+ $schemaldap->start_tls if ${$c->config}{'Model::Proxy'}{'start_tls'};
+ $schemaldap->bind;
+ my $schema = $schemaldap->schema or die ("Searching schema failed: $!");
+ my $attrdef;
+
+ my $user = $c->user->username;
+ my $entry;
+ $c->log->info("Searching for user $user");
+ $mesg = $c->model('User')->search("(&(objectclass=inetOrgPerson)(uid=$user))");
+ $entry = $mesg->entry;
+ my %mods;
+ my %params = %{$c->req->parameters};
+ my $update = 0;
+ foreach my $req (keys %params) {
+ next if $req !~ /(.+)_new/;
+ my $attrname = $1;
+ next if $params{$attrname . '_new'} eq $params{$attrname . '_old'};
+ $c->log->info("Received update request for attribute $attrname");
+ $update = 1;
+ $attrdef = $schema->attribute($attrname) or die ("getting schema failed: $!");
+ if ($$attrdef{'single-value'}) {
+ $entry->replace($attrname => $params{$attrname . '_new' }) or $c->log->info($!);
+ } else {
+ $entry->delete($attrname => $params{$attrname . '_old'});
+ $entry->add($attrname => $params{$attrname . '_new'});
+ }
+ if ($update) {
+ $mesg = $entry->update;
+ push @{${$c->stash}{'errors'}},$mesg->error if $mesg->code;
+ }
+ }
+
+
+ $mesg = $c->model('User')->search("(&(objectclass=inetOrgPerson)(uid=$user))");
+ $c->log->info($mesg->error) if $mesg->code;
+ $entry = $mesg->entry;
+ $c->log->info($mesg->error) if $mesg->code;
+
+ my @values;
+ my @attributes = $entry->attributes;
+ my @may;
+ my @addable_attrs = @attributes;
+ my @ocs;
+ my @must;
+ @ocs = $entry->get_value("objectClass");
+ foreach my $oc (@ocs) {
+ foreach my $attr ($schema->must($oc)) {
+ push @must,$$attr{'name'} if not grep /$$attr{'name'}/,@must;
+ }
+ }
+
+ foreach my $attr (sort @attributes) {
+ next if ($attr eq "objectClass");
+ next if grep /$attr/,@{${$c->config}{'Controller::User'}{'skip_attrs'}};
+ my @vals = $entry->get_value($attr);
+ $attrdef = $schema->attribute($attr) or die ("getting schema failed: $!");
+ my %valhash = (
+ name => $attr,
+ values => \@vals,
+ desc => $$attrdef{'desc'},
+ );
+ if (! grep /^$attr$/, @{${$c->config}{'Controller::User'}{'uneditable_attrs'}}) {
+ $valhash{'editable'} = 1;
+ }
+ if (! $$attrdef{'single-value'} && $valhash{'editable'}) { $valhash{'addable'} = 1; }
+ if (! grep /$attr/,@must) { $valhash{'removable'} = 1; }
+ push @values, \%valhash;
+ }
+ foreach my $oc (@ocs) {
+ foreach my $attrdef ($schema->may($oc)) {
+ my $attrname = $$attrdef{'name'};
+ grep /$attrname/,@may or
+ grep /$attrname/,@attributes or
+ grep /$attrname/,@{${$c->config}{'Controller::User'}{'uneditable_attrs'}} or
+ grep /$attrname/,@{${$c->config}{'Controller::User'}{'skip_attrs'}} or
+ push @may, $attrname;
+ }
+ }
+ @may = sort @may;
+ $c->stash({ username => $user,
+ values => \@values,
+ attrdef => $attrdef,
+ may => \@may,
+ must => \@must,
+ });
+}
+
+sub add : Local {
+ my ( $self, $c) = @_;
+ my ($mesg,$entry,$user,$attr,$value);
+ $attr = $c->req->param('attribute');
+ $value = $c->req->param('value');
+ $user = $c->user->username;
+ $c->log->info("Searching for user $user");
+ $mesg = $c->model('User')->search("(&(objectclass=inetOrgPerson)(uid=$user))");
+ $entry = $mesg->entry;
+ $entry->add( $attr => $value);
+ $c->log->info("Adding $attr = $value to user $user");
+ $entry->update;
+ push @{${$c->stash}{'errors'}},$mesg->error if $mesg->code;
+ $c->log->info($mesg->error);
+ $c->res->redirect('/user');
+}
+
+sub delete : Local : Args(2) {
+ my ( $self, $c, $attrname,$attrvalue) = @_;
+ my ($mesg,$entry,$user);
+ $user = $c->user->username;
+ $c->log->info("Searching for user $user");
+ $mesg = $c->model('User')->search("(&(objectclass=inetOrgPerson)(uid=$user))");
+ $entry = $mesg->entry;
+ $c->log->info("Deleting $attrname = $attrvalue from user $user");
+ $entry->delete($attrname => $attrvalue);
+ $entry->update;
+ push @{${$c->stash}{'errors'}},$mesg->error if $mesg->code;
+ $c->log->info($mesg->error);
+ $c->res->redirect('/user');
+}
+
+sub password : Local {
+ my ( $self, $c) = @_;
+ my ($mesg,$newpass,$cipher);
+ if ( not defined $c->req->param('password') or not defined $c->req->param('newpassword1') or not defined $c->req->param('newpassword2')) {
+ #if ( not defined $c->req->param('newpassword1') or not defined $c->req->param('newpassword2')) {
+ $c->detach;
+ }
+ if ($c->req->param('newpassword1') eq $c->req->param('newpassword2')) {
+ $newpass = $c->req->param('newpassword1');
+ } else {
+ push @{${$c->stash}{'errors'}},"New passwords dont match";
+ }
+ my $pp = Net::LDAP::Control::PasswordPolicy->new;
+ $mesg = $c->model('User')->set_password(
+ oldpasswd => $c->req->param('password'),
+ newpasswd => $newpass,
+ control => [ $pp ],
+ );
+ if ($mesg->code) {
+ my $perror = $mesg->error;
+ push @{${$c->stash}{'errors'}},"Password change failed: $perror";
+ $c->detach;
+ } else {
+ # re-encrypt the new password and forward to user view
+ my $keyprefix = sprintf("%02x%02x%02x",split /\./,$c->req->address);
+ $cipher = Crypt::CBC->new( -key => $keyprefix . $c->sessionid,
+ -cipher => 'Blowfish'
+ ) or die $!;
+ $c->session->{enc_password} = $cipher->encrypt($newpass);
+ push @{${$c->stash}{'errors'}},"Password change succeeded";
+ $c->res->redirect('/user');
+ }
+
+
+}
+
+sub firstlogin : Local {
+ my ( $self, $c ) = @_;
+ my ($mesg,$newpass,$cipher);
+
+ if (! $c->authenticate({
+ username => $c->req->param('username'),
+ password => $c->req->param('key')}) ) {
+ $c->stash(errors => ['An error occurred']);
+ $c->res->redirect('/user');
+ }
+
+ if ( not defined $c->req->param('newpassword1') or not defined $c->req->param('newpassword2')) {
+ $c->detach;
+ }
+ if ($c->req->param('newpassword1') eq $c->req->param('newpassword2')) {
+ $newpass = $c->req->param('newpassword1');
+ } else {
+ push @{${$c->stash}{'errors'}},"New passwords dont match";
+ }
+ my $pp = Net::LDAP::Control::PasswordPolicy->new;
+ $mesg = $c->model('User')->set_password(
+ #oldpasswd => $c->req->param('password'),
+ newpasswd => $newpass,
+ control => [ $pp ],
+ );
+ if ($mesg->code) {
+ my $perror = $mesg->error;
+ push @{${$c->stash}{'errors'}},"Password change failed: $perror";
+ $c->detach;
+ } else {
+ # re-encrypt the new password and forward to user view
+ my $keyprefix = sprintf("%02x%02x%02x",split /\./,$c->req->address);
+ $cipher = Crypt::CBC->new( -key => $keyprefix . $c->sessionid,
+ -cipher => 'Blowfish'
+ ) or die $!;
+ $c->session->{enc_password} = $cipher->encrypt($newpass);
+ push @{${$c->stash}{'errors'}},"Password change succeeded";
+ $c->res->redirect('/user');
+ }
+
+}
+
+sub login : Local {
+ my ( $self, $c ) = @_;
+ if ($c->authenticate({ username => $c->req->param('username'),
+ password => $c->req->param('password') || $c->req->param('key')}) ) {
+ $c->res->redirect('/user');
+ } else {
+ #TODO: ppolicy ....
+ $c->stash(errors => ['Incorrect username or password']);
+ $c->stash(template => 'index.tt');
+ $c->forward('/index');
+ }
+ return $c->error;
+}
+
+sub logout : Local {
+ my ( $self, $c ) = @_;
+ $c->delete_session;
+ $c->res->redirect('/');
+}
+
+=head1 AUTHOR
+
+Buchan Milne
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->meta->make_immutable;
+
+1;
diff --git a/lib/CatDap/I18N/af.po b/lib/CatDap/I18N/af.po
new file mode 100644
index 0000000..7181fbe
--- /dev/null
+++ b/lib/CatDap/I18N/af.po
@@ -0,0 +1,119 @@
+#: lib/CatDap/Controller/register.pm:51
+msgid "Addresses do not match"
+msgstr "Die addresse verskil"
+
+#: lib/CatDap/Controller/register.pm:71
+msgid "An account already exists with this email address"
+msgstr "'n Rekening met hierde epos adres bestaan reeds"
+
+#: lib/CatDap/Controller/register.pm:76
+msgid "An account already exists with this username"
+msgstr "'n Rekening met hierdie gebruikersnaam bestaan reeds"
+
+#. ($errors)
+#: lib/CatDap/Controller/register.pm:120
+msgid ""
+"An error occured sending the email, but your account was created. Please try "
+"the password recovery process f you entered the correct email address: %1"
+msgstr ""
+"Daar was 'n fout met die stuur van die aktiverings epos, maar jou rekening "
+"is geskep. Probeer die wagwoord herwinnings proses as die epos adres korrek "
+"was"
+
+#: root/register/complete.tt:16
+msgid "Check your mail for activation instructions"
+msgstr "Kyk jou epos vir aktiverings instruksies"
+
+#: root/register/index.tt:34
+msgid "Confirm Email address"
+msgstr "Bevestig epos adres"
+
+#: lib/CatDap/Controller/register.pm:106
+msgid "Dear"
+msgstr "Liewe"
+
+#: root/register/index.tt:30
+msgid "Email address"
+msgstr "Epos adres"
+
+#: root/register/index.tt:22
+msgid "First name"
+msgstr "Voornaam"
+
+#: lib/CatDap/Controller/register.pm:54
+msgid "Incorrect validation text, please try again"
+msgstr "Inkorrekte teks van die prentjie, probeer weer"
+
+#: lib/CatDap/Controller/register.pm:48
+msgid "Invalid email address"
+msgstr "Ongeldige epos adres"
+
+#: lib/CatDap/Controller/register.pm:44
+msgid "Invalid username"
+msgstr "Ongeldige gebruikersnaam"
+
+#: root/index.tt:28 root/index.tt:6
+msgid "Login"
+msgstr "Teken in"
+
+#: lib/CatDap/Controller/register.pm:113
+msgid "Mageia Identity Activation"
+msgstr "Mageia Identiteit Aktivering"
+
+#: root/index.tt:22
+msgid "Password"
+msgstr "Wagwoord"
+
+#: root/index.tt:29 root/register/index.tt:43 root/register/index.tt:6
+msgid "Register"
+msgstr "Registreer"
+
+#: root/register/complete.tt:6
+msgid "Registration completed"
+msgstr "Registrasie voltooi"
+
+#: root/register/complete.tt:15
+msgid "Registration was successful."
+msgstr "Registrasie was suksesvol"
+
+#: root/register/index.tt:26
+msgid "Surname"
+msgstr "Van"
+
+#: lib/CatDap/Controller/register.pm:60
+msgid "The"
+msgstr "Die"
+
+#: lib/CatDap/Controller/register.pm:57
+msgid "The first name supplied contains illegal characters"
+msgstr "Die verskafte noemnaam sluit ongeldige karakters in"
+
+#: lib/CatDap/Controller/register.pm:61
+msgid "The surname supplied contains illegal characters"
+msgstr "Die verskafte van sluit ondeldige karakters in"
+
+#: lib/CatDap/Controller/register.pm:107
+msgid "To activate your account, please follow the link below.\n"
+msgstr "Om U rekening te aktiveer, volg asseblief die volgende skakel.\n"
+
+#: root/index.tt:18 root/register/index.tt:18
+msgid "Username"
+msgstr "Gebruikersnaam"
+
+#: lib/CatDap/Controller/register.pm:106
+msgid ""
+"Your Mageia indentity has been successfully created, but requires "
+"activation.\n"
+msgstr ""
+
+#: root/index.tt:28
+msgid "or"
+msgstr "of"
+
+#: lib/CatDap/Controller/register.pm:60
+msgid "supplied contains unprintable characters"
+msgstr "verskaf sluit in ongeldige karakters"
+
+#: lib/CatDap/Controller/register.pm:60
+msgid "surname"
+msgstr "Van"
diff --git a/lib/CatDap/I18N/fr.po b/lib/CatDap/I18N/fr.po
new file mode 100644
index 0000000..56305a3
--- /dev/null
+++ b/lib/CatDap/I18N/fr.po
@@ -0,0 +1,133 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: 2010-10-19 21:07+0100\n"
+"Last-Translator: Michael Scherer <misc@zarb.org>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: lib/CatDap/Controller/register.pm:51
+msgid "Addresses do not match"
+msgstr "Les adresses ne correspondent pas"
+
+#: lib/CatDap/Controller/register.pm:71
+msgid "An account already exists with this email address"
+msgstr ""
+
+#: lib/CatDap/Controller/register.pm:76
+msgid "An account already exists with this username"
+msgstr "Un compte existe déjà pour ce nom d'utilisateur"
+
+#. ($errors)
+#: lib/CatDap/Controller/register.pm:120
+msgid "An error occured sending the email, but your account was created. Please try the password recovery process f you entered the correct email address: %1"
+msgstr ""
+
+#: root/register/complete.tt:16
+msgid "Check your mail for activation instructions"
+msgstr ""
+
+#: root/register/index.tt:34
+msgid "Confirm Email address"
+msgstr ""
+
+#: lib/CatDap/Controller/register.pm:106
+msgid "Dear"
+msgstr "Cher(e)"
+
+#: root/register/index.tt:30
+msgid "Email address"
+msgstr "Adresse mail"
+
+#: root/register/index.tt:22
+msgid "First name"
+msgstr ""
+
+#: lib/CatDap/Controller/register.pm:54
+msgid "Incorrect validation text, please try again"
+msgstr ""
+
+#: lib/CatDap/Controller/register.pm:48
+msgid "Invalid email address"
+msgstr "Adresse mail invalide"
+
+#: lib/CatDap/Controller/register.pm:44
+msgid "Invalid username"
+msgstr "Nom d'utilisateur invalide"
+
+#: root/index.tt:33
+#: root/index.tt:6
+msgid "Login"
+msgstr "Login"
+
+#: lib/CatDap/Controller/register.pm:113
+msgid "Mageia Identity Activation"
+msgstr "Activation de l'identité Mageia"
+
+#: root/index.tt:27
+msgid "Password"
+msgstr "Mot de passe"
+
+#: root/index.tt:34
+#: root/register/index.tt:43
+#: root/register/index.tt:6
+msgid "Register"
+msgstr "S'enregistrer"
+
+#: root/register/complete.tt:6
+msgid "Registration completed"
+msgstr ""
+
+#: root/register/complete.tt:15
+msgid "Registration was successful."
+msgstr ""
+
+#: root/register/index.tt:26
+msgid "Surname"
+msgstr "Surnom"
+
+#: lib/CatDap/Controller/register.pm:60
+msgid "The"
+msgstr ""
+
+#: lib/CatDap/Controller/register.pm:57
+msgid "The first name supplied contains illegal characters"
+msgstr ""
+
+#: lib/CatDap/Controller/register.pm:61
+msgid "The surname supplied contains illegal characters"
+msgstr ""
+
+#: lib/CatDap/Controller/register.pm:107
+msgid "To activate your account, please follow the link below.\n"
+msgstr "Pour activer votre compte, merci de suivre le lien ci dessous."
+
+#: root/index.tt:23
+#: root/register/index.tt:18
+msgid "Username"
+msgstr "Nom d'utilisateur"
+
+#: lib/CatDap/Controller/register.pm:106
+msgid "Your Mageia indentity has been successfully created, but requires activation.\n"
+msgstr ""
+
+#: root/index.tt:33
+msgid "or"
+msgstr "ou"
+
+#: lib/CatDap/Controller/register.pm:60
+msgid "supplied contains unprintable characters"
+msgstr ""
+
+#: lib/CatDap/Controller/register.pm:60
+msgid "surname"
+msgstr ""
+
diff --git a/lib/CatDap/I18N/messages.pot b/lib/CatDap/I18N/messages.pot
new file mode 100644
index 0000000..d4bddea
--- /dev/null
+++ b/lib/CatDap/I18N/messages.pot
@@ -0,0 +1,129 @@
+# SOME DESCRIPTIVE TITLE.
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=CHARSET\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: lib/CatDap/Controller/register.pm:51
+msgid "Addresses do not match"
+msgstr ""
+
+#: lib/CatDap/Controller/register.pm:71
+msgid "An account already exists with this email address"
+msgstr ""
+
+#: lib/CatDap/Controller/register.pm:76
+msgid "An account already exists with this username"
+msgstr ""
+
+#. ($errors)
+#: lib/CatDap/Controller/register.pm:120
+msgid "An error occured sending the email, but your account was created. Please try the password recovery process f you entered the correct email address: %1"
+msgstr ""
+
+#: root/register/complete.tt:16
+msgid "Check your mail for activation instructions"
+msgstr ""
+
+#: root/register/index.tt:34
+msgid "Confirm Email address"
+msgstr ""
+
+#: lib/CatDap/Controller/register.pm:106
+msgid "Dear"
+msgstr ""
+
+#: root/register/index.tt:30
+msgid "Email address"
+msgstr ""
+
+#: root/register/index.tt:22
+msgid "First name"
+msgstr ""
+
+#: lib/CatDap/Controller/register.pm:54
+msgid "Incorrect validation text, please try again"
+msgstr ""
+
+#: lib/CatDap/Controller/register.pm:48
+msgid "Invalid email address"
+msgstr ""
+
+#: lib/CatDap/Controller/register.pm:44
+msgid "Invalid username"
+msgstr ""
+
+#: root/index.tt:28 root/index.tt:6
+msgid "Login"
+msgstr ""
+
+#: lib/CatDap/Controller/register.pm:113
+msgid "Mageia Identity Activation"
+msgstr ""
+
+#: root/index.tt:22
+msgid "Password"
+msgstr ""
+
+#: root/index.tt:29 root/register/index.tt:43 root/register/index.tt:6
+msgid "Register"
+msgstr ""
+
+#: root/register/complete.tt:6
+msgid "Registration completed"
+msgstr ""
+
+#: root/register/complete.tt:15
+msgid "Registration was successful."
+msgstr ""
+
+#: root/register/index.tt:26
+msgid "Surname"
+msgstr ""
+
+#: lib/CatDap/Controller/register.pm:60
+msgid "The"
+msgstr ""
+
+#: lib/CatDap/Controller/register.pm:57
+msgid "The first name supplied contains illegal characters"
+msgstr ""
+
+#: lib/CatDap/Controller/register.pm:61
+msgid "The surname supplied contains illegal characters"
+msgstr ""
+
+#: lib/CatDap/Controller/register.pm:107
+msgid "To activate your account, please follow the link below.\n"
+msgstr ""
+
+#: root/index.tt:18 root/register/index.tt:18
+msgid "Username"
+msgstr ""
+
+#: lib/CatDap/Controller/register.pm:106
+msgid "Your Mageia indentity has been successfully created, but requires activation.\n"
+msgstr ""
+
+#: root/index.tt:28
+msgid "or"
+msgstr ""
+
+#: lib/CatDap/Controller/register.pm:60
+msgid "supplied contains unprintable characters"
+msgstr ""
+
+#: lib/CatDap/Controller/register.pm:60
+msgid "surname"
+msgstr ""
diff --git a/lib/CatDap/Model/Proxy.pm b/lib/CatDap/Model/Proxy.pm
new file mode 100644
index 0000000..ba7e5dc
--- /dev/null
+++ b/lib/CatDap/Model/Proxy.pm
@@ -0,0 +1,40 @@
+package CatDap::Model::Proxy;
+
+use strict;
+use warnings;
+use base qw/Catalyst::Model::LDAP/;
+
+__PACKAGE__->config(
+ host => 'ldap.mageia.org',
+ base => 'ou=People,dc=mageia,dc=org',
+ dn => '',
+ password => '',
+ start_tls => 0,
+ start_tls_options => { verify => 'require' },
+ options => {}, # Options passed to search
+);
+
+=head1 NAME
+
+CatDap::Model::Proxy - LDAP Catalyst model component
+
+=head1 SYNOPSIS
+
+See L<CatDap>.
+
+=head1 DESCRIPTION
+
+LDAP Catalyst model component.
+
+=head1 AUTHOR
+
+Buchan Milne
+
+=head1 LICENSE
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+1;
diff --git a/lib/CatDap/Model/User.pm b/lib/CatDap/Model/User.pm
new file mode 100644
index 0000000..2d726fc
--- /dev/null
+++ b/lib/CatDap/Model/User.pm
@@ -0,0 +1,40 @@
+package CatDap::Model::User;
+
+use strict;
+use warnings;
+#use base qw/Catalyst::Model::LDAP/;
+use Moose;
+use namespace::autoclean;
+extends 'Catalyst::Model::LDAP::FromAuthentication';
+
+__PACKAGE__->config(
+ base => 'ou=People,dc=mageia,dc=org',
+ dn => 'cn=dummy',
+ password => 'dummy',
+);
+
+
+=head1 NAME
+
+CatDap::Model::User - LDAP Catalyst model component
+
+=head1 SYNOPSIS
+
+See L<CatDap>.
+
+=head1 DESCRIPTION
+
+LDAP Catalyst model component.
+
+=head1 AUTHOR
+
+Buchan Milne
+
+=head1 LICENSE
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+1;
diff --git a/lib/CatDap/View/Email.pm b/lib/CatDap/View/Email.pm
new file mode 100644
index 0000000..5e7e96d
--- /dev/null
+++ b/lib/CatDap/View/Email.pm
@@ -0,0 +1,33 @@
+package CatDap::View::Email;
+
+use strict;
+use base 'Catalyst::View::Email';
+
+__PACKAGE__->config(
+ stash_key => 'email'
+);
+
+=head1 NAME
+
+CatDap::View::Email - Email View for CatDap
+
+=head1 DESCRIPTION
+
+View for sending email from CatDap.
+
+=head1 AUTHOR
+
+Buchan Milne
+
+=head1 SEE ALSO
+
+L<CatDap>
+
+=head1 LICENSE
+
+This library is free software, you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+1;
diff --git a/lib/CatDap/View/TT.pm b/lib/CatDap/View/TT.pm
new file mode 100644
index 0000000..0cd6acb
--- /dev/null
+++ b/lib/CatDap/View/TT.pm
@@ -0,0 +1,36 @@
+package CatDap::View::TT;
+
+use strict;
+use warnings;
+
+use base 'Catalyst::View::TT';
+
+__PACKAGE__->config(
+ TEMPLATE_EXTENSION => '.tt',
+ render_die => 1,
+);
+
+=head1 NAME
+
+CatDap::View::TT - TT View for CatDap
+
+=head1 DESCRIPTION
+
+TT View for CatDap.
+
+=head1 SEE ALSO
+
+L<CatDap>
+
+=head1 AUTHOR
+
+Buchan Milne
+
+=head1 LICENSE
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
+1;
diff --git a/root/favicon.ico b/root/favicon.ico
new file mode 100644
index 0000000..5ad723d
--- /dev/null
+++ b/root/favicon.ico
Binary files differ
diff --git a/root/index.tt b/root/index.tt
new file mode 100644
index 0000000..3b43806
--- /dev/null
+++ b/root/index.tt
@@ -0,0 +1,34 @@
+[% MACRO l(text, args) BLOCK;
+ c.localize(text, args);
+END; %]
+
+
+<h2>[% l('Login') %]</h2>
+
+<span class="error">
+[% FOREACH error IN errors %]
+[% error %]<br/>
+[% END %]
+</span>
+
+<form method=post action="/user">
+
+<table border=0>
+<tr>
+<td>[% l('Username') %]</td>
+<td><input type=text name="username" value=[% c.request.params.username %]></td>
+</tr>
+<tr>
+<td>[% l('Password') %]</td>
+<td><input type=password name='password'></td>
+</tr>
+<tr>
+<td></td>
+<td colspan=1>
+ <input type='Submit' value='[% l('Login') %]'> [% l('or') %]
+ <a href="/register">[% l('Register') %]</a>
+</td>
+</tr>
+</table>
+
+
diff --git a/root/register/check.tt b/root/register/check.tt
new file mode 100644
index 0000000..aab8e33
--- /dev/null
+++ b/root/register/check.tt
@@ -0,0 +1,4 @@
+<h2>Success</h2>
+<p>
+[% message %]
+
diff --git a/root/register/complete.tt b/root/register/complete.tt
new file mode 100644
index 0000000..a4f07e2
--- /dev/null
+++ b/root/register/complete.tt
@@ -0,0 +1,16 @@
+[% MACRO l(text, args) BLOCK;
+ c.localize(text, args);
+END; %]
+
+
+<h2>[% l('Registration completed') %]</h2>
+
+<span class="error">
+[% FOREACH error IN errors %]
+[% error %]<br/>
+[% END %]
+</span>
+
+<p>
+[% l('Registration was successful.') %]
+[% l('Check your mail for activation instructions') %]
diff --git a/root/register/index.tt b/root/register/index.tt
new file mode 100644
index 0000000..f509316
--- /dev/null
+++ b/root/register/index.tt
@@ -0,0 +1,45 @@
+[% MACRO l(text, args) BLOCK;
+ c.localize(text, args);
+END; %]
+
+
+<h2>[% l('Register') %]</h2>
+
+<span class="error">
+[% FOREACH error IN errors %]
+[% error %]<br/>
+[% END %]
+</span>
+
+<form method=POST action="/register/check">
+
+<table border=0>
+<tr>
+<td>[% l('Username') %]</td>
+<td><input type=text name="uid" value=[% c.request.params.uid %]></td>
+</tr>
+<tr>
+<td>[% l('First name') %]</td>
+<td><input type=text name='gn' value=[% c.request.params.gn %]></td>
+</tr>
+<tr>
+<td>[% l('Surname') %]</td>
+<td><input type=text name='sn' value=[% c.request.params.sn %]></td>
+</tr>
+<tr>
+<td>[% l('Email address') %]</td>
+<td><input type=text name='mail1' value=[% c.request.params.mail1 %]></td>
+</tr>
+<tr>
+<td>[% l('Confirm Email address') %]</td>
+<td><input type=text name='mail2' value=[% c.request.params.mail2 %]></td>
+</tr>
+<tr>
+<td><img src=/register/captcha></td>
+<td><input type=text name=validate>
+</tr>
+<tr>
+<td></td>
+<td colspan=1><input type='Submit' value='[% l('Register') %]'>
+</tr>
+</table>
diff --git a/root/static/images/btn_120x50_built.png b/root/static/images/btn_120x50_built.png
new file mode 100644
index 0000000..c709fd6
--- /dev/null
+++ b/root/static/images/btn_120x50_built.png
Binary files differ
diff --git a/root/static/images/btn_120x50_built_shadow.png b/root/static/images/btn_120x50_built_shadow.png
new file mode 100644
index 0000000..15142fe
--- /dev/null
+++ b/root/static/images/btn_120x50_built_shadow.png
Binary files differ
diff --git a/root/static/images/btn_120x50_powered.png b/root/static/images/btn_120x50_powered.png
new file mode 100644
index 0000000..7249b47
--- /dev/null
+++ b/root/static/images/btn_120x50_powered.png
Binary files differ
diff --git a/root/static/images/btn_120x50_powered_shadow.png b/root/static/images/btn_120x50_powered_shadow.png
new file mode 100644
index 0000000..e6876c0
--- /dev/null
+++ b/root/static/images/btn_120x50_powered_shadow.png
Binary files differ
diff --git a/root/static/images/btn_88x31_built.png b/root/static/images/btn_88x31_built.png
new file mode 100644
index 0000000..007b5db
--- /dev/null
+++ b/root/static/images/btn_88x31_built.png
Binary files differ
diff --git a/root/static/images/btn_88x31_built_shadow.png b/root/static/images/btn_88x31_built_shadow.png
new file mode 100644
index 0000000..ccf4624
--- /dev/null
+++ b/root/static/images/btn_88x31_built_shadow.png
Binary files differ
diff --git a/root/static/images/btn_88x31_powered.png b/root/static/images/btn_88x31_powered.png
new file mode 100644
index 0000000..8f0cd9f
--- /dev/null
+++ b/root/static/images/btn_88x31_powered.png
Binary files differ
diff --git a/root/static/images/btn_88x31_powered_shadow.png b/root/static/images/btn_88x31_powered_shadow.png
new file mode 100644
index 0000000..aa776fa
--- /dev/null
+++ b/root/static/images/btn_88x31_powered_shadow.png
Binary files differ
diff --git a/root/static/images/catalyst_logo.png b/root/static/images/catalyst_logo.png
new file mode 100644
index 0000000..21f1cac
--- /dev/null
+++ b/root/static/images/catalyst_logo.png
Binary files differ
diff --git a/script/catdap_cgi.pl b/script/catdap_cgi.pl
new file mode 100755
index 0000000..dea0834
--- /dev/null
+++ b/script/catdap_cgi.pl
@@ -0,0 +1,30 @@
+#!/usr/bin/env perl
+
+use Catalyst::ScriptRunner;
+Catalyst::ScriptRunner->run('CatDap', 'CGI');
+
+1;
+
+=head1 NAME
+
+catdap_cgi.pl - Catalyst CGI
+
+=head1 SYNOPSIS
+
+See L<Catalyst::Manual>
+
+=head1 DESCRIPTION
+
+Run a Catalyst application as a cgi script.
+
+=head1 AUTHORS
+
+Catalyst Contributors, see Catalyst.pm
+
+=head1 COPYRIGHT
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
diff --git a/script/catdap_create.pl b/script/catdap_create.pl
new file mode 100755
index 0000000..18c0239
--- /dev/null
+++ b/script/catdap_create.pl
@@ -0,0 +1,57 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+
+use Catalyst::ScriptRunner;
+Catalyst::ScriptRunner->run('CatDap', 'Create');
+
+1;
+
+=head1 NAME
+
+catdap_create.pl - Create a new Catalyst Component
+
+=head1 SYNOPSIS
+
+catdap_create.pl [options] model|view|controller name [helper] [options]
+
+ Options:
+ --force don't create a .new file where a file to be created exists
+ --mechanize use Test::WWW::Mechanize::Catalyst for tests if available
+ --help display this help and exits
+
+ Examples:
+ catdap_create.pl controller My::Controller
+ catdap_create.pl -mechanize controller My::Controller
+ catdap_create.pl view My::View
+ catdap_create.pl view MyView TT
+ catdap_create.pl view TT TT
+ catdap_create.pl model My::Model
+ catdap_create.pl model SomeDB DBIC::Schema MyApp::Schema create=dynamic\
+ dbi:SQLite:/tmp/my.db
+ catdap_create.pl model AnotherDB DBIC::Schema MyApp::Schema create=static\
+ dbi:Pg:dbname=foo root 4321
+
+ See also:
+ perldoc Catalyst::Manual
+ perldoc Catalyst::Manual::Intro
+
+=head1 DESCRIPTION
+
+Create a new Catalyst Component.
+
+Existing component files are not overwritten. If any of the component files
+to be created already exist the file will be written with a '.new' suffix.
+This behavior can be suppressed with the C<-force> option.
+
+=head1 AUTHORS
+
+Catalyst Contributors, see Catalyst.pm
+
+=head1 COPYRIGHT
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
diff --git a/script/catdap_fastcgi.pl b/script/catdap_fastcgi.pl
new file mode 100755
index 0000000..110db29
--- /dev/null
+++ b/script/catdap_fastcgi.pl
@@ -0,0 +1,47 @@
+#!/usr/bin/env perl
+
+use Catalyst::ScriptRunner;
+Catalyst::ScriptRunner->run('CatDap', 'FastCGI');
+
+1;
+
+=head1 NAME
+
+catdap_fastcgi.pl - Catalyst FastCGI
+
+=head1 SYNOPSIS
+
+catdap_fastcgi.pl [options]
+
+ Options:
+ -? -help display this help and exits
+ -l --listen Socket path to listen on
+ (defaults to standard input)
+ can be HOST:PORT, :PORT or a
+ filesystem path
+ -n --nproc specify number of processes to keep
+ to serve requests (defaults to 1,
+ requires -listen)
+ -p --pidfile specify filename for pid file
+ (requires -listen)
+ -d --daemon daemonize (requires -listen)
+ -M --manager specify alternate process manager
+ (FCGI::ProcManager sub-class)
+ or empty string to disable
+ -e --keeperr send error messages to STDOUT, not
+ to the webserver
+
+=head1 DESCRIPTION
+
+Run a Catalyst application as fastcgi.
+
+=head1 AUTHORS
+
+Catalyst Contributors, see Catalyst.pm
+
+=head1 COPYRIGHT
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
diff --git a/script/catdap_i18n.sh b/script/catdap_i18n.sh
new file mode 100755
index 0000000..8c52d1c
--- /dev/null
+++ b/script/catdap_i18n.sh
@@ -0,0 +1,16 @@
+#!/bin/sh
+
+app=CatDap
+
+if [ $# -eq 0 ]
+then
+ xgettext.pl --output=lib/$app/I18N/messages.pot --directory=lib --directory=root
+ for po in lib/$app/I18N/*.po
+ do
+ file=`basename $po`
+ loc=${file%%.po}
+ msgmerge --update lib/$app/I18N/$loc.po lib/$app/I18N/messages.pot
+ done
+else
+ msginit --input=lib/$app/I18N/messages.pot --output=lib/$app/I18N/$1.po --locale=$1
+fi
diff --git a/script/catdap_server.pl b/script/catdap_server.pl
new file mode 100755
index 0000000..b75954b
--- /dev/null
+++ b/script/catdap_server.pl
@@ -0,0 +1,60 @@
+#!/usr/bin/env perl
+
+BEGIN {
+ $ENV{CATALYST_SCRIPT_GEN} = 40;
+}
+
+use Catalyst::ScriptRunner;
+Catalyst::ScriptRunner->run('CatDap', 'Server');
+
+1;
+
+=head1 NAME
+
+catdap_server.pl - Catalyst Test Server
+
+=head1 SYNOPSIS
+
+catdap_server.pl [options]
+
+ -d --debug force debug mode
+ -f --fork handle each request in a new process
+ (defaults to false)
+ -? --help display this help and exits
+ -h --host host (defaults to all)
+ -p --port port (defaults to 3000)
+ -k --keepalive enable keep-alive connections
+ -r --restart restart when files get modified
+ (defaults to false)
+ -rd --restart_delay delay between file checks
+ (ignored if you have Linux::Inotify2 installed)
+ -rr --restart_regex regex match files that trigger
+ a restart when modified
+ (defaults to '\.yml$|\.yaml$|\.conf|\.pm$')
+ --restart_directory the directory to search for
+ modified files, can be set mulitple times
+ (defaults to '[SCRIPT_DIR]/..')
+ --follow_symlinks follow symlinks in search directories
+ (defaults to false. this is a no-op on Win32)
+ --background run the process in the background
+ --pidfile specify filename for pid file
+
+ See also:
+ perldoc Catalyst::Manual
+ perldoc Catalyst::Manual::Intro
+
+=head1 DESCRIPTION
+
+Run a Catalyst Testserver for this application.
+
+=head1 AUTHORS
+
+Catalyst Contributors, see Catalyst.pm
+
+=head1 COPYRIGHT
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
+
diff --git a/script/catdap_test.pl b/script/catdap_test.pl
new file mode 100755
index 0000000..43f9849
--- /dev/null
+++ b/script/catdap_test.pl
@@ -0,0 +1,40 @@
+#!/usr/bin/env perl
+
+use Catalyst::ScriptRunner;
+Catalyst::ScriptRunner->run('CatDap', 'Test');
+
+1;
+
+=head1 NAME
+
+catdap_test.pl - Catalyst Test
+
+=head1 SYNOPSIS
+
+catdap_test.pl [options] uri
+
+ Options:
+ --help display this help and exits
+
+ Examples:
+ catdap_test.pl http://localhost/some_action
+ catdap_test.pl /some_action
+
+ See also:
+ perldoc Catalyst::Manual
+ perldoc Catalyst::Manual::Intro
+
+=head1 DESCRIPTION
+
+Run a Catalyst action from the command line.
+
+=head1 AUTHORS
+
+Catalyst Contributors, see Catalyst.pm
+
+=head1 COPYRIGHT
+
+This library is free software. You can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=cut
diff --git a/t/01app.t b/t/01app.t
new file mode 100644
index 0000000..00299fd
--- /dev/null
+++ b/t/01app.t
@@ -0,0 +1,10 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More;
+
+BEGIN { use_ok 'Catalyst::Test', 'CatDap' }
+
+ok( request('/')->is_success, 'Request should succeed' );
+
+done_testing();
diff --git a/t/02pod.t b/t/02pod.t
new file mode 100644
index 0000000..3d1bab1
--- /dev/null
+++ b/t/02pod.t
@@ -0,0 +1,10 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More;
+
+eval "use Test::Pod 1.14";
+plan skip_all => 'Test::Pod 1.14 required' if $@;
+plan skip_all => 'set TEST_POD to enable this test' unless $ENV{TEST_POD};
+
+all_pod_files_ok();
diff --git a/t/03podcoverage.t b/t/03podcoverage.t
new file mode 100644
index 0000000..4e1c6e7
--- /dev/null
+++ b/t/03podcoverage.t
@@ -0,0 +1,10 @@
+#!/usr/bin/env perl
+use strict;
+use warnings;
+use Test::More;
+
+eval "use Test::Pod::Coverage 1.04";
+plan skip_all => 'Test::Pod::Coverage 1.04 required' if $@;
+plan skip_all => 'set TEST_POD to enable this test' unless $ENV{TEST_POD};
+
+all_pod_coverage_ok();
diff --git a/t/controller_register.t b/t/controller_register.t
new file mode 100644
index 0000000..526e1a1
--- /dev/null
+++ b/t/controller_register.t
@@ -0,0 +1,9 @@
+use strict;
+use warnings;
+use Test::More;
+
+BEGIN { use_ok 'Catalyst::Test', 'CatDap' }
+BEGIN { use_ok 'CatDap::Controller::register' }
+
+ok( request('/register')->is_success, 'Request should succeed' );
+done_testing();
diff --git a/t/controller_user.t b/t/controller_user.t
new file mode 100644
index 0000000..f8e26a0
--- /dev/null
+++ b/t/controller_user.t
@@ -0,0 +1,9 @@
+use strict;
+use warnings;
+use Test::More;
+
+BEGIN { use_ok 'Catalyst::Test', 'CatDap' }
+BEGIN { use_ok 'CatDap::Controller::user' }
+
+ok( request('/user')->is_success, 'Request should succeed' );
+done_testing();
diff --git a/t/model_Proxy.t b/t/model_Proxy.t
new file mode 100644
index 0000000..c98a6db
--- /dev/null
+++ b/t/model_Proxy.t
@@ -0,0 +1,6 @@
+use strict;
+use warnings;
+use Test::More tests => 2;
+
+use_ok('Catalyst::Test', 'CatDap');
+use_ok('CatDap::Model::Proxy');
diff --git a/t/model_User.t b/t/model_User.t
new file mode 100644
index 0000000..543866c
--- /dev/null
+++ b/t/model_User.t
@@ -0,0 +1,6 @@
+use strict;
+use warnings;
+use Test::More tests => 2;
+
+use_ok('Catalyst::Test', 'CatDap');
+use_ok('CatDap::Model::User');
diff --git a/t/view_Email.t b/t/view_Email.t
new file mode 100644
index 0000000..757075d
--- /dev/null
+++ b/t/view_Email.t
@@ -0,0 +1,7 @@
+use strict;
+use warnings;
+use Test::More;
+
+BEGIN { use_ok 'CatDap::View::Email' }
+
+done_testing();
diff --git a/t/view_TT.t b/t/view_TT.t
new file mode 100644
index 0000000..0adc968
--- /dev/null
+++ b/t/view_TT.t
@@ -0,0 +1,7 @@
+use strict;
+use warnings;
+use Test::More;
+
+BEGIN { use_ok 'CatDap::View::TT' }
+
+done_testing();