Bivio::Auth::Realm
# Copyright (c) 1999-2011 bivio Software, Inc. All rights reserved. # $Id$ package Bivio::Auth::Realm; use strict; use Bivio::Base 'Collection.Attributes'; # C<Auth.Realm> defines the authorization policy for # L<Auth.Role|Auth.Role> and # L<Agent.Task|Agent.Task>. # A task is authorized by # L<can_user_execute_task|"can_user_execute_task">. # # Subclasses define the actual authorization policies. # # # # id : string # # Primary id of the owner or the RealmType as an int. # # owner : Biz.Model::RealmOwner # # The particular instance of this realm. Only used in the case of # clubs and users. General does not have an owner. # # owner_name : string # # Named retrieved from realm owner. Not defined for the general realm. # Always use this value instead of owner-E<gt>get('name'). # # type : Auth.RealmType # # Type of this realm. our($_TRACE); b_use('IO.Trace'); my($_IDI) = __PACKAGE__->instance_data_index; my($_INITIALIZED) = 0; my($_GENERAL); my($_GENERAL_NAME); my($_PI) = b_use('Type.PrimaryId'); my(@_USED_ROLES) = b_use('Auth.Role')->get_non_zero_list; my($_RT) = b_use('Auth.RealmType'); my($_PS) = b_use('Auth.PermissionSet'); my($_RO) = b_use('Model.RealmOwner'); my($_S) = b_use('Auth.Support'); my($_M) = b_use('Biz.Model'); sub GENERAL_NAME { return $_GENERAL_NAME ||= lc($_RT->GENERAL->get_name); } sub as_string { my($self) = @_; # Pretty prints the realm. my($owner) = $self->unsafe_get('owner'); return ref($self) . '[' . join( ',', $self->get('type')->get_name, $owner ? $self->unsafe_get('owner_name', 'id') : (), ) . ']'; } sub assert_type { my($self, $expect) = @_; $expect = $_RT->from_any($expect); b_die($self, ': not expected ', $expect) unless $self->get('type')->equals($expect); return; } sub can_user_execute_task { my($self, $task, $req) = @_; unless ($task->has_realm_type($self->get('type'))) { _trace($task->get('id'), ': no such task in ', $self->get('type')) if $_TRACE; return 0; } return 0 unless $self->does_user_have_permissions($task->get('permission_set'), $req); return 1 unless my $as = $task->unsafe_get('extra_auth'); # enforce type, since no guarantees caller does, and can_user_execute_task # is a very public interface. return $_S->$as($self, $task, $req) ? 1 : 0; } sub do_default { return _do_default(get_non_zero_list => @_); } sub do_any_group_default { return _do_default(get_any_group_list => @_); } sub does_user_have_permissions { my($self, $perms, $req) = @_; # Does req.auth_user have I<perms> in this realm. $perms = ${$_PS->from_array($perms)} if ref($perms) eq 'ARRAY'; my($fields) = $self->[$_IDI]; return $_S->task_permission_ok( _perm_set_from_all([map({ my($auth_role) = $_; unless (defined($fields->{$auth_role})) { $fields->{$auth_role} = $_S->load_permissions( $self, $auth_role, $req); } $fields->{$auth_role}; } @{$req->get_auth_roles($self)})]), $perms, $req, ); } sub equals { my($self, $that) = @_; # Returns true if I<self> is identical I<that>. return ref($self) eq ref($that) && $self->get('id') eq $that->get('id') ? 1 : 0; } sub equals_by_name_or_id { my($self, $name_or_id) = @_; $name_or_id = '' if !defined($name_or_id) || $name_or_id eq $self->GENERAL_NAME; return grep(($self->unsafe_get($_) || '') eq $name_or_id, qw(owner_name id)) ? 1 : 0; } sub format_email { my($self) = @_; # How to mail to this realm. # This is more than caching. It allows for overriding. my($email) = $self->unsafe_get('_email'); return $email if $email; # Compute and cache (since we are checking anyway) $email = $self->get('owner')->get_request->format_email( $self->get('owner_name')); $self->put(_email => $email); return $email; } sub format_file { my($self) = @_; # Returns the root of the file server. # This is more than caching. It allows to override this value. my($file) = $self->unsafe_get('_file'); return $file if $file; # Compute and cache (since we are checking anyway) $file = $self->get('owner_name'); $self->put(_file => $file); return $file; } sub format_uri { my($self) = @_; # Returns the "home" of this realm, i.e. just its name. # Only works for realms with owners. # This is more than caching. It allows to override this value. my($uri) = $self->unsafe_get('_uri'); return $uri if $uri; # Compute and cache (since we are checking anyway) $uri = $self->get('owner')->format_uri(); $self->put(_uri => $uri); return $uri; } sub get_default_id { my($self) = @_; # Returns the default id for this realm. return $self->get('type')->as_int; } sub get_default_name { my($self) = @_; # Returns the owner name used for the three default realms (general, club, user). return lc($self->get('type')->get_name); } sub get_general { # Returns the singleton instance of the GENERAL realm. return $_GENERAL ||= _new(shift(@_)); } sub get_type { my($proto) = @_; # Returns the RealmType for this realm. # # B<DEPRECATED>. # Get the type from the instance itself otherwise # just from class. return $proto->get('type') if ref($proto); b_die($proto, ': unknown realm class'); } sub has_owner { # Returns true if has I<owner> (same as not is_default). return shift->is_default ? 0 : 1; } sub id_from_any { my($proto) = shift; # Returns the realm_id from I<realm_or_id>. Can be a realm_id, # model with realm_id, instance, or self. my($realm_or_id) = @_ ? @_ : $proto; return ref($realm_or_id) ? __PACKAGE__->is_blesser_of($realm_or_id) ? $realm_or_id->get('id') : $_M->is_blesser_of($realm_or_id) ? $realm_or_id->get('realm_id') : b_die($realm_or_id, ': unhandled reference type') : $_PI->is_specified($realm_or_id) || $proto->is_default_id($realm_or_id) ? $realm_or_id : b_die($realm_or_id, ': not a PrimaryId'); } sub is_default { my($self) = @_; return 1 if $self->get('type') == $_RT->GENERAL; return $self->get('owner')->is_default; } sub is_default_id { my(undef, $id) = @_; return $_RT->is_default_id($id); } sub is_general { my($self) = @_; # Returns true if self is general realm. return $self->get_general->get('id') eq $self->get('id') ? 1 : 0; } sub new { my($proto, $owner, $req) = @_; # If the realm has an I<owner>, it will be saved. If it # has an I<owner_id> or I<owner_name>, the owner will be loaded first. # The owner must exist. b_die("must have owner or call type explicitly") unless $owner; if (ref($owner)) { return $proto->new(lc($owner->get_name), $req) if $_RT->is_blesser_of($owner); } else { #TODO: Deprecate default names to be special, e.g. =user b_die('cannot create model without request') unless ref($req); my($g) = $proto->get_general; return $g if $g->get('id') eq $owner || $owner eq $proto->GENERAL_NAME; $owner = b_use('Cache.RealmOwner') ->get_cache_value($owner, $req); } return $owner->clone if __PACKAGE__->is_blesser_of($owner); return _new($proto, $owner, $req); } sub owner_name_equals { my($self, $name) = @_; return ($self->unsafe_get('owner_name') || '') eq $name ? 1 : 0; } sub _do_default { my($list_method, $proto, $op, $req) = @_; $req->with_user(user => sub { foreach my $r ($_RT->$list_method()) { last unless $req->with_realm( $r->get_name, sub {$op->($req->get('auth_realm'))}, ); } return; }); return; } sub _new { my($proto, $owner, $req) = @_; # Create a new instance. If I<owner> is undef, a GENERAL realm # is created. # Instantiate and initialize with/out owner my($self) = $proto->SUPER::new; $self->[$_IDI] = {}; unless ($owner) { # If there is no owner, then permissions already retrieved from # database. Set "id" to realm_type. my($type) = $_RT->GENERAL; $self->put(id => $type->as_int, type => $type); return $self; } b_die($owner, ': owner not a Model::RealmOwner') unless $_RO->is_blesser_of($owner); #TODO: Change this so everyone knows realm_id? my($id) = $owner->get('realm_id'); b_die($id, ': owner must have valid id (must be loaded)') unless $id; $self->put( owner => $owner, id => $id, owner_name => $owner->get('name'), type => $owner->get('realm_type'), ); return $self; } sub _perm_set_from_all { my($permissions) = @_; # Calculate the sum of all given permissions. my($perm_set) = $_PS->get_min; foreach my $perms (@$permissions) { $perm_set |= $perms; } return $perm_set; } 1;