diff --git a/lemonldap-ng-portal/MANIFEST b/lemonldap-ng-portal/MANIFEST index 58bf51c51..9f00afd89 100644 --- a/lemonldap-ng-portal/MANIFEST +++ b/lemonldap-ng-portal/MANIFEST @@ -48,6 +48,7 @@ lib/Lemonldap/NG/Portal/Auth/Slave.pm lib/Lemonldap/NG/Portal/Auth/SSL.pm lib/Lemonldap/NG/Portal/Auth/Twitter.pm lib/Lemonldap/NG/Portal/Auth/WebID.pm +lib/Lemonldap/NG/Portal/Captcha/SecurityImage.pm lib/Lemonldap/NG/Portal/CDC.pm lib/Lemonldap/NG/Portal/CertificateResetByMail/Custom.pm lib/Lemonldap/NG/Portal/CertificateResetByMail/Demo.pm diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Captcha/SecurityImage.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Captcha/SecurityImage.pm new file mode 100644 index 000000000..6e0c4a594 --- /dev/null +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Captcha/SecurityImage.pm @@ -0,0 +1,189 @@ +package Lemonldap::NG::Portal::Captcha::SecurityImage; + +use strict; +use Mouse; +use MIME::Base64; +use GD::SecurityImage use_magick => 1; + +our $VERSION = '2.0.12'; + +extends 'Lemonldap::NG::Portal::Main::Plugin'; + +has width => ( + is => 'rw', + lazy => 1, + default => sub { $_[0]->{conf}->{captchaWidth} || 220 } +); +has height => ( + is => 'rw', + lazy => 1, + default => sub { $_[0]->{conf}->{captchaHeight} || 40 } +); +has lines => ( + is => 'rw', + lazy => 1, + default => sub { $_[0]->{conf}->{captchaLines} || 5 } +); +has scramble => ( + is => 'rw', + lazy => 1, + default => sub { $_[0]->{conf}->{captchaScramble} || 1 } +); +has fgColor => ( + is => 'rw', + lazy => 1, + default => sub { $_[0]->{conf}->{captchaFg} || '#403030' } +); +has bgColor => ( + is => 'rw', + lazy => 1, + default => sub { $_[0]->{conf}->{captchaBg} || '#FF644B' } +); +has rndmax => ( + is => 'rw', + lazy => 1, + default => sub { $_[0]->{conf}->{captcha_size} || 6 } +); +has timeout => ( + is => 'rw', + lazy => 1, + default => sub { $_[0]->{conf}->{formTimeout} } +); + +has ott => ( + is => 'rw', + lazy => 1, + default => sub { + my $ott = $_[0]->{p}->loadModule('::Lib::OneTimeToken'); + $ott->timeout( $_[0]->timeout ); + return $ott; + } +); + +sub init { + my ($self) = @_; + if ( $self->conf->{captcha_mail_enabled} + || $self->conf->{captcha_login_enabled} + || $self->conf->{captcha_register_enabled} ) + { + $self->addUnauthRoute( renewcaptcha => '_sendCaptcha', ['GET'] ); + } + return 1; +} + +# Internal methods +sub _getCaptcha { + my ($self) = @_; + my $image = GD::SecurityImage->new( + width => $self->width, + height => $self->height, + lines => $self->lines, + gd_font => 'Giant', + scramble => $self->scramble, + rndmax => $self->rndmax, + ); + $image->random; + $image->create( 'normal', 'default', $self->fgColor, $self->bgColor ); + my ( $imageData, $mimeType, $rdm ) = $image->out( force => 'png' ); + my $img = 'data:image/png;base64,' . encode_base64( $imageData, '' ); + my $token = $self->ott->createToken( { captcha => $rdm } ); + return ( $token, $img ); +} + +sub _sendCaptcha { + my ( $self, $req ) = @_; + $self->logger->info("User request for captcha renew"); + my ( $token, $image ) = $self->_getCaptcha($req); + + return $self->p->sendJSONresponse( $req, + { newtoken => $token, newimage => $image } ); +} + +sub _validate_captcha_token { + my ( $self, $token, $value ) = @_; + my $s = $self->ott->getToken($token); + unless ($s) { + $self->logger->warn("Captcha token $token isn't valid"); + return 0; + } + unless ( $s->{captcha} eq $value ) { + $self->logger->notice('Bad captcha response'); + return 0; + } + $self->logger->debug('Good captcha response'); + return 1; +} + +sub _get_captcha_html { + my ( $self, $req, $src ) = @_; + + my $sp = $self->p->staticPrefix; + $sp =~ s/\/*$/\//; + + return $self->loadTemplate( + $req, + 'captcha', + params => { + STATIC_PREFIX => $sp, + CAPTCHA_SRC => $src, + CAPTCHA_SIZE => $self->rndmax, + } + ); +} + +# New API +sub check_captcha { + my ( $self, $req ) = @_; + my $token = $req->param('token'); + unless ($token) { + $self->logger->warn("No token provided for Captcha::SecurityImage"); + return 0; + } + + my $value = $req->param('captcha'); + unless ($value) { + $self->logger->warn("No response provided for Captcha::SecurityImage"); + return 0; + } + + return $self->_validate_captcha_token( $token, $value ); +} + +sub init_captcha { + my ( $self, $req ) = @_; + + my ( $token, $image ) = $self->_getCaptcha; + $self->logger->debug('Prepare captcha'); + $req->token($token); + $req->captchaHtml( $self->_get_captcha_html( $req, $image ) ); + + # DEPRECATED: Compatibility with old templates + $req->captcha($image); +} + +# ####### +# Old API +# TODO: Remove this in 3.0 +# ####### + +sub validateCaptcha { + my ( $self, $token, $value ) = @_; + return $self->_validate_captcha_token( $token, $value ); +} + +sub setCaptcha { + my ( $self, $req ) = @_; + $self->init_captcha($req); +} + +sub sendCaptcha { + my ( $self, $req ) = @_; + return $self->_sendCaptcha($req); +} + +sub getCaptcha { + my ( $self, $req ) = @_; + return $self->_getCaptcha($req); +} + +1; diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/Captcha.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/Captcha.pm index 08b81cef4..671d43b46 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/Captcha.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Lib/Captcha.pm @@ -1,109 +1,40 @@ package Lemonldap::NG::Portal::Lib::Captcha; +# Old Captcha API, this is only a wrapper around Captcha::SecurityImage + use strict; use Mouse; use MIME::Base64; -use GD::SecurityImage use_magick => 1; our $VERSION = '2.0.12'; extends 'Lemonldap::NG::Common::Module'; -has width => ( +has module => ( is => 'rw', - lazy => 1, - default => sub { $_[0]->{conf}->{captchaWidth} || 220 } -); -has height => ( - is => 'rw', - lazy => 1, - default => sub { $_[0]->{conf}->{captchaHeight} || 40 } -); -has lines => ( - is => 'rw', - lazy => 1, - default => sub { $_[0]->{conf}->{captchaLines} || 5 } -); -has scramble => ( - is => 'rw', - lazy => 1, - default => sub { $_[0]->{conf}->{captchaScramble} || 1 } -); -has fgColor => ( - is => 'rw', - lazy => 1, - default => sub { $_[0]->{conf}->{captchaFg} || '#403030' } -); -has bgColor => ( - is => 'rw', - lazy => 1, - default => sub { $_[0]->{conf}->{captchaBg} || '#FF644B' } -); -has rndmax => ( - is => 'rw', - lazy => 1, - default => sub { $_[0]->{conf}->{captcha_size} || 6 } -); -has timeout => ( - is => 'rw', - lazy => 1, - default => sub { $_[0]->{conf}->{formTimeout} } -); - -has ott => ( - is => 'rw', - lazy => 1, - default => sub { - my $ott = $_[0]->{p}->loadModule('::Lib::OneTimeToken'); - $ott->timeout( $_[0]->timeout ); - return $ott; - } + handles => [ + qw(setCaptcha validateCaptcha getCaptcha ott width height lines scramble fgColor bgColor rndmax timeout ) + ] ); sub init { - return 1; -} - -# Returns secret + a HTML image src content -sub getCaptcha { my ($self) = @_; - my $image = GD::SecurityImage->new( - width => $self->width, - height => $self->height, - lines => $self->lines, - gd_font => 'Giant', - scramble => $self->scramble, - rndmax => $self->rndmax, - ); - $image->random; - $image->create( 'normal', 'default', $self->fgColor, $self->bgColor ); - my ( $imageData, $mimeType, $rdm ) = $image->out( force => 'png' ); - my $img = 'data:image/png;base64,' . encode_base64( $imageData, '' ); - my $token = $self->ott->createToken( { captcha => $rdm } ); - return ( $token, $img ); -} -sub validateCaptcha { - my ( $self, $token, $value ) = @_; - my $s = $self->ott->getToken($token); - unless ($s) { - $self->logger->warn("Captcha token $token isn't valid"); + if ( $self->conf->{captcha} ) { + $self->logger->error( "The Lib::Captcha API is not compatible" + . " with custom Captcha module" ); return 0; } - unless ( $s->{captcha} eq $value ) { - $self->logger->notice('Bad captcha response'); - return 0; + else { + my $module = $self->p->loadModule("::Captcha::SecurityImage"); + if ($module) { + $self->module($module); + return 1; + } + else { + return 0; + } } - $self->logger->debug('Good captcha response'); - return 1; -} - -sub setCaptcha { - my ( $self, $req ) = @_; - my ( $token, $image ) = $self->getCaptcha; - $self->logger->debug('Prepare captcha'); - $req->token($token); - $req->captcha($image); } 1; diff --git a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/RESTServer.pm b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/RESTServer.pm index e675feee9..5fb8c8dbc 100644 --- a/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/RESTServer.pm +++ b/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Plugins/RESTServer.pm @@ -18,7 +18,6 @@ # * DELETE /sessions/my : ask for global logout (if GlobalLogout plugin is on) # # - Authentication -# * GET /renewcaptcha : get token and captcha image # * POST /sessions//?auth : authenticate with a fixed # sessionId # * Note that the "getCookie" method (authentification via SOAP) exists for @@ -71,7 +70,6 @@ our $VERSION = '2.0.14'; extends qw( Lemonldap::NG::Portal::Main::Plugin - Lemonldap::NG::Portal::Lib::Captcha ); has configStorage => ( @@ -116,7 +114,6 @@ has exportedAttr => ( } } ); -has captcha => ( is => 'rw' ); has ott => ( is => 'rw', lazy => 1, @@ -134,13 +131,6 @@ sub init { my ($self) = @_; my @parents = ('Lemonldap::NG::Portal::Main::Plugin'); my $add = 0; - if ( $self->conf->{captcha_mail_enabled} - || $self->conf->{captcha_login_enabled} - || $self->conf->{captcha_register_enabled} ) - { - $self->captcha( $self->p->loadModule('::Lib::Captcha') ) or return 0; - $self->addUnauthRoute( renewcaptcha => 'sendCaptcha', ['GET'] ); - } if ( $self->conf->{restConfigServer} ) { push @parents, 'Lemonldap::NG::Common::Conf::RESTServer'; $add++; @@ -641,15 +631,6 @@ sub removeSessions { return $self->p->sendJSONresponse( $req, { result => $nbr } ); } -sub sendCaptcha { - my ( $self, $req ) = @_; - $self->logger->info("User request for captcha renew"); - my ( $token, $image ) = $self->captcha->getCaptcha($req); - - return $self->p->sendJSONresponse( $req, - { newtoken => $token, newimage => $image } ); -} - sub pwdReset { my ( $self, $req ) = @_;