Revision history for Catalyst-Plugin-Authentication-Store-DBIx-Class
+ * Added support for self checking roles
* Fix doc bugs. RT#87372
* Fix calling User->can() as a class method. RT#90715
user_model => 'MyApp::User',
role_relation => 'roles',
role_field => 'rolename',
+ check_roles => 'check_roles',
+ check_roles_any => 'check_roles_any',
}
}
}
role_field => 'rolename',
ignore_fields_in_find => [ 'remote_name' ],
use_userdata_from_session => 1,
+ check_roles => 'check_roles',
+ check_roles_any => 'check_roles_any',
}
}
}
that if use_userdata_from_session is enabled, this config parameter
is not used at all.
+=item check_roles
+
+If this option of set, checking the user has all the roles will be delegated to the
+specified method on the user row. This allows for you to override the role
+check, if you want to check virtual roles, or make super roles etc.
+
+You should set the value to the name of the method on the user row to call
+
+ __PACKAGE__->config('Plugin::Authentication' => {
+ realms => {
+ members => {
+ store => {
+ check_roles => 'custom_check_roles',
+ check_roles_any => 'custom_check_roles_any',
+ }
+ }
+ }
+ });
+
+
+\@roles, and \@wanted_roles will be passed, where \@roles is the list of user roles
+and \@wanted_roles is the list of wanted roles.
+
+Should return true if user has the role.
+
+You will have to check the whole set yourself, eg this is the default behaviour
+when not setting 'check_roles'
+
+ use Set::Object;
+
+ sub custom_check_roles {
+ my ( $self, $roles, $wanted_roles ) = @_;
+
+ my $have = Set::Object->new(@$roles);
+ my $need = Set::Object->new(@$wanted_roles);
+
+ if ( $have->superset($need) ) {
+ return 1;
+ }
+ }
+
+=item check_roles_any
+
+Same as check_roles, except it's for checking that the user has at least one of
+the roles
+
+This is the default when check_roles_any is not set
+
+ use Set::Object;
+
+ sub custom_check_roles_any {
+ my ( $self, $roles, $wanted_roles ) = @_;
+
+ my $have = Set::Object->new(@$roles);
+ my $need = Set::Object->new(@$wanted_roles);
+
+ if ( $have->intersection($need)->size > 0 ) {
+ return 1;
+ }
+ }
+
=back
=head1 USAGE
return {
session => 1,
- roles => 1,
+ roles => {
+ self_check => $self->config->{check_roles} || 0,,
+ self_check_any => $self->config->{check_roles_any} || 0,
+ },
};
}
+#will only be used if $config->{check_roles} is set
+sub check_roles {
+ my ( $self, @wanted_roles ) = @_;
+
+ my @roles = $self->roles;
+ my $name = $self->config->{check_roles};
+
+ return $self->_user->$name( \@roles, \@wanted_roles );
+}
+
+#will only be used if $config->{check_roles_any} is set
+sub check_roles_any {
+ my ( $self, @wanted_roles ) = @_;
+
+ my @roles = $self->roles;
+ my $name = $self->config->{check_roles_any};
+
+ return $self->_user->$name( \@roles, \@wanted_roles );
+}
sub roles {
my ( $self ) = shift;
Delegates handling of the C<< can >> method to the underlying user row.
+=head2 check_roles
+
+Calls the specified check_roles method on the underlying user row.
+
+Passes \@roles, \@wanted_roles, where @roles is the list of roles,
+and @wanted_roles is the list of wanted roles
+
+=head2 check_roles_any
+
+Calls the specified check_roles_any method on the underlying user row.
+
+Passes \@roles, \@wanted_roles, where @roles is the list of roles,
+and @wanted_roles is the list of wanted roles
+
=head1 BUGS AND LIMITATIONS
None known currently, please email the author if you find any.
or plan skip_all =>
"DBD::SQLite is required for this test";
- eval { require DBIx::Class }
- or plan skip_all =>
- "DBIx::Class is required for this test";
-
plan tests => 19;
use TestApp;
or plan skip_all =>
"DBD::SQLite is required for this test";
- eval { require DBIx::Class }
- or plan skip_all =>
- "DBIx::Class is required for this test";
-
eval { require Catalyst::Plugin::Session;
die unless $Catalyst::Plugin::Session::VERSION >= 0.02 }
or plan skip_all =>
or plan skip_all =>
"DBD::SQLite is required for this test";
- eval { require DBIx::Class }
- or plan skip_all =>
- "DBIx::Class is required for this test";
-
eval { require Catalyst::Plugin::Authorization::Roles }
or plan skip_all =>
"Catalyst::Plugin::Authorization::Roles is required for this test";
- plan tests => 8;
+ plan tests => 10;
use TestApp;
TestApp->config( {
ok( my $res = request('http://localhost/user_login?username=nuffin&password=much&detach=is_admin_user'), 'request ok' );
is( $res->content, 'failed', 'user is not an admin and a user' );
}
+
+# test superuser role override fails (not enabled)
+{
+ ok( my $res = request('http://localhost/user_login?username=mark&password=secret&detach=is_admin'), 'request ok' );
+ is( $res->content, 'failed', 'user is not an admin' );
+}
or plan skip_all =>
"DBD::SQLite is required for this test";
- eval { require DBIx::Class }
- or plan skip_all =>
- "DBIx::Class is required for this test";
-
eval { require Catalyst::Plugin::Authorization::Roles }
or plan skip_all =>
"Catalyst::Plugin::Authorization::Roles is required for this test";
- plan tests => 8;
+ plan tests => 10;
use TestApp;
TestApp->config( {
ok( my $res = request('http://localhost/user_login?username=joeuser&password=hackme&detach=is_admin_user'), 'request ok' );
is( $res->content, 'failed', 'user is not an admin and a user' );
}
+
+# test superuser role override fails (not enabled)
+{
+ ok( my $res = request('http://localhost/user_login?username=graeme&password=supersecret&detach=is_admin'), 'request ok' );
+ is( $res->content, 'failed', 'user is not an admin' );
+}
or plan skip_all =>
"DBD::SQLite is required for this test";
- eval { require DBIx::Class }
- or plan skip_all =>
- "DBIx::Class is required for this test";
-
eval { require Catalyst::Plugin::Session;
die unless $Catalyst::Plugin::Session::VERSION >= 0.02 }
or plan skip_all =>
or plan skip_all =>
"DBD::SQLite is required for this test";
- eval { require DBIx::Class }
- or plan skip_all =>
- "DBIx::Class is required for this test";
-
eval { require Catalyst::Plugin::Authorization::Roles }
or plan skip_all =>
"Catalyst::Plugin::Authorization::Roles is required for this test";
or plan skip_all =>
"DBD::SQLite is required for this test";
- eval { require DBIx::Class }
- or plan skip_all =>
- "DBIx::Class is required for this test";
-
eval { require Catalyst::Plugin::Authorization::Roles }
or plan skip_all =>
"Catalyst::Plugin::Authorization::Roles is required for this test";
or plan skip_all =>
"DBD::SQLite is required for this test";
- eval { require DBIx::Class }
- or plan skip_all =>
- "DBIx::Class is required for this test";
-
eval { require Catalyst::Plugin::Session;
die unless $Catalyst::Plugin::Session::VERSION >= 0.02 }
or plan skip_all =>
--- /dev/null
+#!perl
+
+use strict;
+use warnings;
+use DBI;
+use File::Path;
+use FindBin;
+use Test::More;
+use lib "$FindBin::Bin/lib";
+
+BEGIN {
+ eval { require DBD::SQLite }
+ or plan skip_all =>
+ "DBD::SQLite is required for this test";
+
+ eval { require Catalyst::Plugin::Authorization::Roles }
+ or plan skip_all =>
+ "Catalyst::Plugin::Authorization::Roles is required for this test";
+
+ plan tests => 29;
+
+ use TestApp;
+ TestApp->config( {
+ name => 'TestApp',
+ authentication => {
+ default_realm => "users",
+ realms => {
+ users => {
+ credential => {
+ 'class' => "Password",
+ 'password_field' => 'password',
+ 'password_type' => 'clear'
+ },
+ store => {
+ 'class' => 'DBIx::Class',
+ 'user_model' => 'TestApp::User',
+ 'role_relation' => 'roles',
+ 'role_field' => 'role',
+ 'check_roles' => 't_check_roles',
+ 'check_roles_any' => 't_check_roles_any'
+ },
+ },
+ },
+ },
+ } );
+
+ TestApp->setup(
+ qw/Authentication
+ Authorization::Roles
+ /
+ );
+}
+
+use Catalyst::Test 'TestApp';
+
+# test user's admin access
+{
+ ok( my $res = request('http://localhost/user_login?username=jayk&password=letmein&detach=is_admin'), 'request ok' );
+ is( $res->content, 'ok', 'user is an admin' );
+}
+
+# test unauthorized user's admin access
+{
+ ok( my $res = request('http://localhost/user_login?username=nuffin&password=much&detach=is_admin'), 'request ok' );
+ is( $res->content, 'failed', 'user is not an admin' );
+}
+
+# test multiple auth roles
+{
+ ok( my $res = request('http://localhost/user_login?username=jayk&password=letmein&detach=is_admin_user'), 'request ok' );
+ is( $res->content, 'ok', 'user is an admin and a user' );
+}
+
+# test multiple unauth roles
+{
+ ok( my $res = request('http://localhost/user_login?username=nuffin&password=much&detach=is_admin_user'), 'request ok' );
+ is( $res->content, 'failed', 'user is not an admin and a user' );
+}
+
+# test assert_any_user_role
+{
+ ok( my ( $res, $c )= ctx_request('http://localhost/user_login?username=nuffin&password=much&detach=is_any_admin_user'), 'request ok' );
+ is( $res->content, 'ok', 'user is user' );
+ is ( my @roles = $c->user->roles, 1, 'only 1 role' );
+ is ( $roles[0], 'user', 'role is user' );
+}
+
+# test assert_any_user_role
+{
+ ok( my ( $res, $c )= ctx_request('http://localhost/user_login?username=jayk&password=letmein&detach=is_any_admin_user'), 'request ok' );
+ is( $res->content, 'ok', 'user is user and an admin' );
+ is ( my @roles = $c->user->roles, 2, '2 roles' );
+ is ( $roles[0], 'admin', 'role is user' );
+ is ( $roles[1], 'user', 'role is admin' );
+}
+
+# test superuser role override
+{
+ ok( my ( $res, $c )= ctx_request('http://localhost/user_login?username=mark&password=secret&detach=is_admin_user'), 'request ok' );
+ is( $res->content, 'ok', 'superuser role is all roles' );
+ is ( my @roles = $c->user->roles, 1, 'only 1 role' );
+ is ( $roles[0], 'superadmin', 'role is user' );
+}
+
+# test superuser role override none existant roles
+{
+ ok( my ( $res, $c )= ctx_request('http://localhost/user_login?username=mark&password=secret&detach=is_nonexistant_roles'), 'request ok' );
+ is( $res->content, 'ok', 'superuser role is all roles' );
+ is ( my @roles = $c->user->roles, 1, 'only 1 role' );
+ is ( $roles[0], 'superadmin', 'role is user' );
+}
+
+# test superuser role override any none existant roles
+{
+ ok( my ( $res, $c )= ctx_request('http://localhost/user_login?username=mark&password=secret&detach=is_any_nonexistant_role'), 'request ok' );
+ is( $res->content, 'ok', 'superuser role is all roles' );
+ is ( my @roles = $c->user->roles, 1, 'only 1 role' );
+ is ( $roles[0], 'superadmin', 'role is user' );
+}
--- /dev/null
+#!perl
+
+use strict;
+use warnings;
+use DBI;
+use File::Path;
+use FindBin;
+use Test::More;
+use lib "$FindBin::Bin/lib";
+
+BEGIN {
+ eval { require DBD::SQLite }
+ or plan skip_all =>
+ "DBD::SQLite is required for this test";
+
+ eval { require Catalyst::Plugin::Authorization::Roles }
+ or plan skip_all =>
+ "Catalyst::Plugin::Authorization::Roles is required for this test";
+
+ plan tests => 29;
+
+ use TestApp;
+ TestApp->config( {
+ name => 'TestApp',
+ authentication => {
+ default_realm => "users",
+ realms => {
+ users => {
+ credential => {
+ 'class' => "Password",
+ 'password_field' => 'password',
+ 'password_type' => 'clear'
+ },
+ store => {
+ 'class' => 'DBIx::Class',
+ 'user_model' => 'TestApp::User',
+ 'role_column' => 'role_text',
+ 'check_roles' => 't_check_roles',
+ 'check_roles_any' => 't_check_roles_any'
+ },
+ },
+ },
+ },
+ } );
+
+ TestApp->setup(
+ qw/Authentication
+ Authorization::Roles
+ /
+ );
+}
+
+use Catalyst::Test 'TestApp';
+
+# test user's admin access
+{
+ ok( my $res = request('http://localhost/user_login?username=joeuser&password=hackme&detach=is_admin'), 'request ok' );
+ is( $res->content, 'ok', 'user is an admin' );
+}
+
+# test unauthorized user's admin access
+{
+ ok( my $res = request('http://localhost/user_login?username=jayk&password=letmein&detach=is_admin'), 'request ok' );
+ is( $res->content, 'failed', 'user is not an admin' );
+}
+
+# test multiple auth roles
+{
+ ok( my $res = request('http://localhost/user_login?username=nuffin&password=much&detach=is_admin_user'), 'request ok' );
+ is( $res->content, 'ok', 'user is an admin and a user' );
+}
+
+# test multiple unauth roles
+{
+ ok( my $res = request('http://localhost/user_login?username=joeuser&password=hackme&detach=is_admin_user'), 'request ok' );
+ is( $res->content, 'failed', 'user is not an admin and a user' );
+}
+
+# test assert_any_user_role
+{
+ ok( my ( $res, $c )= ctx_request('http://localhost/user_login?username=joeuser&password=hackme&detach=is_any_admin_user'), 'request ok' );
+ is( $res->content, 'ok', 'user is user' );
+ is ( my @roles = $c->user->roles, 1, 'only 1 role' );
+ is ( $roles[0], 'admin', 'role is admin' );
+}
+
+# test assert_any_user_role
+{
+ ok( my ( $res, $c )= ctx_request('http://localhost/user_login?username=nuffin&password=much&detach=is_any_admin_user'), 'request ok' );
+ is( $res->content, 'ok', 'user is user and an admin' );
+ is ( my @roles = $c->user->roles, 2, '2 roles' );
+ is ( $roles[0], 'user', 'role is user' );
+ is ( $roles[1], 'admin', 'role is admin' );
+}
+
+# test superuser role override
+{
+ ok( my ( $res, $c )= ctx_request('http://localhost/user_login?username=graeme&password=supersecret&detach=is_admin_user'), 'request ok' );
+ is( $res->content, 'ok', 'superuser role is all roles' );
+ is ( my @roles = $c->user->roles, 1, 'only 1 role' );
+ is ( $roles[0], 'superadmin', 'role is user' );
+}
+
+# test superuser role override none existant roles
+{
+ ok( my ( $res, $c )= ctx_request('http://localhost/user_login?username=graeme&password=supersecret&detach=is_nonexistant_roles'), 'request ok' );
+ is( $res->content, 'ok', 'superuser role is all roles' );
+ is ( my @roles = $c->user->roles, 1, 'only 1 role' );
+ is ( $roles[0], 'superadmin', 'role is user' );
+}
+
+# test superuser role override any none existant roles
+{
+ ok( my ( $res, $c )= ctx_request('http://localhost/user_login?username=graeme&password=supersecret&detach=is_any_nonexistant_role'), 'request ok' );
+ is( $res->content, 'ok', 'superuser role is all roles' );
+ is ( my @roles = $c->user->roles, 1, 'only 1 role' );
+ is ( $roles[0], 'superadmin', 'role is user' );
+}
}
}
+sub is_any_admin_user : Global {
+ my ( $self, $c ) = @_;
+
+ eval {
+ if ( $c->assert_any_user_role( qw/admin user/ ) ) {
+ $c->res->body( 'ok' );
+ }
+ };
+ if ($@) {
+ $c->res->body( 'failed' );
+ }
+}
+
+sub is_nonexistant_roles: Global {
+ my ( $self, $c ) = @_;
+
+ eval {
+ if ( $c->assert_user_roles( qw/madeUProle baconHater/ ) ) {
+ $c->res->body( 'ok' );
+ }
+ };
+ if ($@) {
+ $c->res->body( 'failed' );
+ }
+}
+
+sub is_any_nonexistant_role: Global {
+ my ( $self, $c ) = @_;
+
+ eval {
+ if ( $c->assert_any_user_role( qw/madeUProle baconHater/ ) ) {
+ $c->res->body( 'ok' );
+ }
+ };
+ if ($@) {
+ $c->res->body( 'failed' );
+ }
+}
+
+
sub set_usersession : Global {
my ( $self, $c, $value ) = @_;
$c->user_session->{foo} = $value;
INSERT INTO user VALUES (2, 'spammer', 'bob@spamhaus.com', 'broken', 'disabled', NULL, NULL);
INSERT INTO user VALUES (3, 'jayk', 'j@cpants.org', 'letmein', 'active', NULL, NULL);
INSERT INTO user VALUES (4, 'nuffin', 'nada@mucho.net', 'much', 'registered', 'user admin', NULL);
+ INSERT INTO user VALUES (5, 'mark', 'b@con.com', 'secret', 'active', NULL, NULL);
+ INSERT INTO user VALUES (6, 'graeme', 'gr@e.me', 'supersecret', 'active', 'superadmin', NULL);
INSERT INTO role VALUES (1, 'admin');
INSERT INTO role VALUES (2, 'user');
+ INSERT INTO role VALUES (3, 'superadmin');
INSERT INTO user_role VALUES (1, 3, 1);
INSERT INTO user_role VALUES (2, 3, 2);
- INSERT INTO user_role VALUES (3, 4, 2)
+ INSERT INTO user_role VALUES (3, 4, 2);
+ INSERT INTO user_role VALUES (4, 5, 3)
};
__PACKAGE__->config(
__PACKAGE__->many_to_many( roles => 'map_user_role', 'role');
+use Set::Object;
+
+sub t_check_roles {
+ my ( $self, $roles, $wanted_roles ) = @_;
+
+ if ( grep { $_ eq 'superadmin' } @$roles ) {
+ return 1;
+ }
+
+ my $have = Set::Object->new(@$roles);
+ my $need = Set::Object->new(@$wanted_roles);
+
+ if ( $have->superset($need) ) {
+ return 1;
+ }
+}
+
+sub t_check_roles_any {
+ my ( $self, $roles, $wanted_roles ) = @_;
+
+ if ( grep { $_ eq 'superadmin' } @$roles ) {
+ return 1;
+ }
+
+ my $have = Set::Object->new(@$roles);
+ my $need = Set::Object->new(@$wanted_roles);
+
+ if ( $have->intersection($need)->size > 0 ) {
+ return 1;
+ }
+}
+
1;