Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
git-svn-id: svn+ssh://svn.bestpractical.com/svn/jifty.org/jifty/trunk@5851 e84bef0a-9b06-0410-84ba-c4c9edb13aeb
- Loading branch information
Showing
8 changed files
with
427 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
use inc::Module::Install; | ||
|
||
name 'Jifty-Plugin-AuthzLDAP'; | ||
all_from 'lib/Jifty/Plugin/AuthzLDAP.pm'; | ||
|
||
version_from('../../lib/Jifty.pm'); | ||
|
||
build_requires 'Test::More'; | ||
requires('Net::LDAP'); | ||
requires('Cache::MemoryCache'); | ||
|
||
install_share; | ||
|
||
tests('t/*.t'); | ||
auto_install; | ||
WriteAll; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,212 @@ | ||
use strict; | ||
use warnings; | ||
|
||
=head1 NAME | ||
Jifty::Plugin::AuthzLDAP - Jifty plugin to a add dynamic ldap authorization | ||
=head1 DESCRIPTION | ||
Jifty plugin. | ||
Provide ldap authorization with filters table and cache. | ||
=head1 CONFIGURATION NOTES | ||
in etc/config.yml | ||
Plugins: | ||
- AuthzLDAP: | ||
LDAPbind: cn=testldap,ou=admins,dc=myorg,dc=org # | ||
LDAPpass: test # password | ||
LDAPhost: ldap.myorg.org # ldap host | ||
LDAPbase: ou=people,dc=myorg.. # ldap base | ||
LDAPuid: uid # optional | ||
CacheTimout: 20 # minutes, optional, default 20 minutes | ||
in application create a LDAPFilter model | ||
use base qw/Jifty::Plugin::AuthzLDAP::Model::LDAPFilter/; | ||
in LDAPFilter model create your filters, something like | ||
name |filter |is_group | ||
is_admin|(!eduPersonAffiliation=STUDENT)|0 | ||
in_admin|cn=admin,ou=groups,dc=my.org |1 | ||
to protect access to /admin | ||
in "TestApp" application create a lib/TestApp/Dispatcher.pm | ||
use strict; | ||
use warnings; | ||
package TestApp::Dispatcher; | ||
use Jifty::Dispatcher -base; | ||
before '/admin/*' => run { | ||
# Authentication | ||
Jifty->web->tangent(url => '/login') | ||
if (! Jifty->web->current_user->id); | ||
# Authorization | ||
my $user = Jifty->web->current_user->user_object->name; | ||
Jifty->web->tangent(url => '/error/AccessDenied') | ||
if (! Jifty::Plugin::AuthzLDAP->ldapvalidate($user,'is_admin') ); | ||
}; | ||
1 | ||
=head1 SEE ALSO | ||
L<Net::LDAP> | ||
=cut | ||
|
||
|
||
package Jifty::Plugin::AuthzLDAP; | ||
use base qw/Jifty::Plugin/; | ||
|
||
our $VERSION = '0.02'; | ||
|
||
use Net::LDAP; | ||
use Cache::MemoryCache; | ||
|
||
{ | ||
my ($LDAPFilterClass, $LDAP, $cache, %params); | ||
|
||
=head1 METHODS | ||
=head2 init | ||
load config parameters, connect to ldap, create memory cache | ||
=cut | ||
|
||
sub init { | ||
my $self = shift; | ||
my %args = @_; | ||
|
||
my $appname = Jifty->config->framework('ApplicationName'); | ||
$LDAPFilterClass = "Jifty::Plugin::AuthzLDAP::Model::LDAPFilter"; | ||
|
||
$params{'Hostname'} = $args{LDAPhost}; | ||
$params{'base'} = $args{LDAPbase}; | ||
$params{'uid'} = $args{LDAPuid} || "uid"; | ||
$params{'dn'} = $args{LDAPbind}; | ||
$params{'pass'} = $args{LDAPpass}; | ||
$params{'timeout'} = $args{CacheTimout} || "20 minutes"; | ||
|
||
$LDAP = Net::LDAP->new($params{Hostname},async=>1,onerror => 'undef', debug => 0); | ||
|
||
$cache = new Cache::MemoryCache( { 'namespace' => $appname.'AuthzLDAP', | ||
'default_expires_in' => $params{'timeout'} } ); | ||
} | ||
|
||
=head2 BASE CACHE DN LDAP BASE UID PASS LDAPFilterClass | ||
accesors to conf parametres | ||
=cut | ||
|
||
sub LDAPFilterClass { | ||
return $LDAPFilterClass; | ||
} | ||
|
||
sub LDAP { | ||
return $LDAP; | ||
} | ||
|
||
sub DN { | ||
return $params{'dn'}; | ||
} | ||
|
||
sub PASS { | ||
return $params{'pass'}; | ||
} | ||
|
||
sub UID { | ||
return $params{'uid'}; | ||
} | ||
|
||
sub BASE { | ||
return $params{'base'}; | ||
} | ||
|
||
sub CACHE { | ||
return $cache; | ||
} | ||
|
||
} | ||
|
||
=head2 bind | ||
Bind to ldap | ||
=cut | ||
|
||
sub bind { | ||
my $self = shift; | ||
my $msg = $self->LDAP()->bind($self->DN() ,'password' =>$self->PASS()); | ||
unless (not $msg->code) { | ||
Jifty->log->error("Bind to ldap server failed"); | ||
return; | ||
} | ||
} | ||
|
||
=head2 ldapvalidate NAME FILTERNAME | ||
return 1 if NAME validate FILTER or NAME-FILTERNAME in cache | ||
else return 0 | ||
If FILTERNAME is flagged as is_group, search if user is uniquemember of this group | ||
as supported by the Netscape Directory Server | ||
=cut | ||
|
||
sub ldapvalidate { | ||
my ($self, $user, $filtername) = @_; | ||
my $response = 'nok'; | ||
|
||
my $cachekey = $user.'-'.$filtername; | ||
my $cache = $self->CACHE->get($cachekey); | ||
return ($cache eq 'ok')?1:0 if (defined $cache); | ||
|
||
my $record = $self->LDAPFilterClass()->new(); | ||
$record->load_by_cols( name => $filtername); | ||
|
||
# (?) allow use of writing filter in filtername | ||
# TODO: filtername must be cleanned | ||
# my $filter = ($record->filter)?$record->filter:$filtername; | ||
my $filter = $record->ldapfilter; | ||
|
||
$user = $self->UID().'='.$user.','.$self->BASE(); | ||
|
||
# (??) how to catch AuthLDAP bind if it's used? | ||
$self->bind(); | ||
|
||
my $msg; | ||
# manage group as supported by the Netscape Directory Server | ||
if ($record->is_group) { | ||
$msg = $self->LDAP()->compare( $filter, attr=>"uniquemember", value=>$user ); | ||
Jifty->log->debug("search grp: ".$msg->code); | ||
$response = 'ok' if ( $msg->code == 6 ); | ||
} else { | ||
$filter = '('. $filter .')' if ( $filter !~ /^\(/ ); | ||
$msg = $self->LDAP()->search( base => $user, filter => $filter ); | ||
Jifty->log->debug("search: ".$msg->count); | ||
$response = 'ok' if (! $msg->code && $msg->count ); | ||
} | ||
|
||
$self->CACHE->set($cachekey,$response); | ||
|
||
return ( $response eq 'ok' )?1:0; | ||
} | ||
|
||
=head1 AUTHOR | ||
Yves Agostini, <yvesago@cpan.org> | ||
=head1 LICENSE | ||
Copyright 2007-2008 Yves Agostini. All Rights Reserved. | ||
This program is free software and may be modified and distributed under the same terms as Perl itself. | ||
=cut | ||
|
||
1; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
use warnings; | ||
use strict; | ||
|
||
=head1 NAME | ||
Jifty::Plugin::AuthzLDAP::Action::LDAPValidate - action to validate filter | ||
=cut | ||
|
||
package Jifty::Plugin::AuthzLDAP::Action::LDAPValidate; | ||
use base qw/Jifty::Action Jifty::Plugin::AuthzLDAP/; | ||
|
||
our $VERSION = '0.01'; | ||
|
||
=head2 arguments | ||
Return the ticket form field | ||
=cut | ||
|
||
sub arguments { | ||
return ( | ||
{ | ||
name => { | ||
mandatory => 1 | ||
}, | ||
|
||
filter => { | ||
mandatory => 1 | ||
}, | ||
|
||
} | ||
); | ||
|
||
} | ||
|
||
=head2 take_action | ||
Bind on ldap to check the user's password. If it's right, log them in. | ||
Otherwise, throw an error. | ||
=cut | ||
|
||
sub take_action { | ||
my $self = shift; | ||
my $user = $self->argument_value('name'); | ||
my $filter = $self->argument_value('filter'); | ||
|
||
Jifty->log->debug("action: $user $filter"); | ||
|
||
# Bind on ldap | ||
#my $msg = $self->bind(); | ||
|
||
my $msg = $self->ldapvalidate($user,$filter); | ||
|
||
Jifty->log->debug("validate: $msg"); | ||
|
||
if (not $msg) { | ||
$self->result->error( | ||
_('Access denied.') ); | ||
return;} | ||
|
||
return 1; | ||
} | ||
|
||
=head1 AUTHOR | ||
Yves Agostini, <yvesago@cpan.org> | ||
=head1 LICENSE | ||
Copyright 2007-2008 Yves Agostini. All Rights Reserved. | ||
This program is free software and may be modified and distributed under the same terms as Perl itself. | ||
=cut | ||
|
||
1; | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package Jifty::Plugin::AuthzLDAP::Model::LDAPFilter; | ||
use Jifty::DBI::Schema; | ||
use Scalar::Defer; | ||
|
||
our $VERSION = '0.02'; | ||
|
||
=head1 NAME | ||
Jifty::Plugin::AuthzLDAP::Model::LDAPFilter - model for filters | ||
=cut | ||
|
||
use Jifty::Record schema { | ||
column | ||
name => type is 'text', | ||
label is 'Name', | ||
is mandatory, | ||
is distinct; | ||
|
||
column | ||
ldapfilter => type is 'text', | ||
label is 'Filter', | ||
is mandatory; | ||
|
||
column | ||
is_group => type is 'boolean', | ||
label is 'Group'; | ||
|
||
column 'created_on' => | ||
type is 'datetime', | ||
is immutable, | ||
default is defer { DateTime->now }, | ||
filters are 'Jifty::DBI::Filter::DateTime'; | ||
}; | ||
|
||
=head2 create | ||
=cut | ||
|
||
sub create { | ||
my $self = shift; | ||
my %args = (@_); | ||
my (@ret) = $self->SUPER::create(%args); | ||
|
||
return (@ret); | ||
} | ||
|
||
|
||
=head2 current_user_can ACTION | ||
Only superuser can create or edit filters. | ||
Logged-in users can read. | ||
=cut | ||
|
||
sub current_user_can { | ||
my $self = shift; | ||
my $type = shift; | ||
|
||
if ($type eq 'create' || $type eq 'update') { | ||
return 0 if | ||
!$self->current_user->is_superuser; | ||
return 1; | ||
} elsif($type eq 'read') { | ||
return 1 if | ||
$self->current_user->id || $self->current_user->is_superuser; | ||
return 0; | ||
} | ||
|
||
return $self->SUPER::current_user_can($type, @_); | ||
} | ||
|
||
=head1 AUTHOR | ||
Yves Agostini, <yvesago@cpan.org> | ||
=head1 LICENSE | ||
Copyright 2007-2008 Yves Agostini. All Rights Reserved. | ||
This program is free software and may be modified and distributed under the same terms as Perl itself. | ||
=cut | ||
|
||
1; |
Oops, something went wrong.