1use warnings; 2use strict; 3 4package Jifty::CurrentUser; 5 6use base qw/Jifty::Object Class::Accessor::Fast/; 7use Scalar::Util qw(); 8 9__PACKAGE__->mk_accessors(qw(is_superuser is_bootstrap_user)); 10 11=head1 NAME 12 13Jifty::CurrentUser - Base class and basic implementation of current user object 14 15=head1 DESCRIPTION 16 17Most applications need to have a concept of who the current user 18is. So Jifty supports this concept internally. Every L<Jifty::Object> 19(which most things in Jifty are descended from) except the CurrentUser 20itself is instantiated with a L<Jifty::CurrentUser> subclass as a 21parameter to the creator. 22 23This class describes (and implements a trivial version) of the access 24control API that a Jifty application needs to implement to provide 25user-based access control 26 27It's generally expected that your application will override this class 28if you want any sort of access control. 29 30=head2 new 31 32Creates a new L<Jifty::CurrentUser> object. Calls L<_init>, an 33app-specific initialization routine. 34 35If you call it with the C<_bootstrap> argument, Jifty will set the user up as a bootstrap user, who's usually allowed to do just about anything without any access control 36 37=cut 38 39sub new { 40 my $class = shift; 41 my $self = {}; 42 bless $self, (ref $class || $class); 43 my %args = (@_); 44 45 # Make this user a bootstrap user if in bootstrap mode 46 if ( delete $args{'_bootstrap'} ) { $self->is_bootstrap_user(1); } 47 48 # Call _init for app-specific initialization 49 $self->_init(%args); 50 51 return $self; 52} 53 54=head2 _init 55 56Applications should override this method to provide any application-specific user loading code. The built-in 57 58If you do nothing, code similar to this will be called by _init. 59 60 sub _init { 61 my $self = shift; 62 my %args = (@_); 63 if (keys %args and UNIVERSAL::can(Jifty->app_class('Model', 'User'), 'new')) { 64 $self->user_object(Jifty->app_class('Model', 'User')->new(current_user => $self)); 65 $self->user_object->load_by_cols(%args); 66 } 67 return 1; 68 } 69 70That is, it will attempt to load the columns given in the model named C<App::Model::User> (where I<App> is the name of your application class). If your notion of a user object isn't a typical Jifty model or named something else, you will definitely need to override this method. If you need to perform any additional initialization for user objects, you may want to override this as well. 71 72=cut 73 74sub _init { 75 my $self = shift; 76 my %args = (@_); 77 78 # Duck-typing to check to for a user class 79 my $user_class = Jifty->app_class({require => 0}, 'Model', 'User'); 80 if (keys %args and UNIVERSAL::can($user_class, 'new') ) { 81 $self->user_object($user_class->new(current_user => $self)); 82 $self->user_object->load_by_cols(%args); 83 } 84 85 return 1; 86} 87 88=head2 superuser 89 90A convenience constructor that returns a new CurrentUser object that's 91marked as a superuser. Can be called either as a class or object method. 92 93=cut 94 95sub superuser { 96 my $class = shift; 97 $class = ref( $class ) if ref $class; 98 99 # Create the current user object 100 my $self = $class->new(); 101 102 # Make it superuser and send it out 103 $self->is_superuser(1); 104 return $self; 105} 106 107=head2 user_object 108 109This gets or sets your application's user object for the current 110user. Generally, you're expected to set and load it in the L</_init> method 111in your L<Jifty::CurrentUser> subclass. 112 113=cut 114 115sub user_object { 116 my $self = shift; 117 return $self->{'user_object'} unless @_; 118 $self->{'user_object'} = shift; 119 120 # protect ourself from circular refereces to prevent memory leaks 121 if ( $self->{'user_object'}{'_current_user'} == $self ) { 122 Scalar::Util::weaken( $self->{'user_object'}{'_current_user'} ) 123 unless Scalar::Util::isweak( $self->{'user_object'}{'_current_user'} ); 124 $self->{'user_object'}{'_resurrect_current_user'} = 1; 125 } 126 127 return $self->{'user_object'}; 128} 129 130=head2 id 131 132Returns C<0> if we don't have a L<user_object>. When we I<do> have a 133user_object, return that user's id. 134 135=cut 136 137sub id { 138 my $self = shift; 139 140 # This can be a hotspot, so we don't use method calls, instead 141 # directly accessing the value. 142 143 # Make sure we have a user object before trying to ID it 144 return $self->{user_object}->id if $self->{user_object}; 145 146 # No user object, return a null ID 147 return 0; 148} 149 150=head2 current_user 151 152Every class in a Jifty application has a L</current_user> method that 153returns the user who's doing things, in the form of a 154L<Jifty::CurrentUser> object a subclass thereof. For the somewhat 155obvious reason that you can't actually lift yourself up by tugging on 156your own bootstraps, a L<Jifty::CurrentUser> object return I<itself> 157rather than another C<Jifty::CurrentUser> object. 158 159=cut 160 161sub current_user { 162 my $self = shift; 163 return $self; 164} 165 166=head1 AUTHENTICATION AND AUTHORIZATION 167 168To use Jifty's built-in authentication and authorization system, your 169user objects need to implement the following API methods: 170 171=head2 password_is STRING 172 173Your L<user_object> should have a method called C<password_is> which 174returns true if passed a string that matches the user's current 175password. 176 177=cut 178 179sub password_is { 180 my $self = shift; 181 return undef unless ($self->user_object); 182 return($self->user_object->password_is(@_)); 183 184} 185 186=head2 username 187 188Return a string which identifies the user in some way. 189 190=cut 191 192sub username { 193 my $self = shift; 194 return undef unless ($self->user_object); 195 return($self->user_object->brief_description(@_)); 196} 197 198=head2 auth_token 199 200Return a string which proves that the user is who they claim to be. A 201simple way to do this, for example, would be to hash the username and 202some server-side secret. 203 204=cut 205 206sub auth_token { 207 my $self = shift; 208 return undef unless ($self->user_object); 209 return ($self->user_object->auth_token); 210 211} 212 213=head1 RIGHTS AND ACCESS CONTROL 214 215In any system that relies on users' rights to perform actions, it's 216sometimes necessary to walk around the access control system. There 217are two primary cases for this: 218 219=cut 220 221=head2 is_superuser 222 223Sometimes, while the system is running, you need to do something on 224behalf of a user that they shouldn't be able to do themselves. Maybe 225you need to let a new user sign up for your service (You don't want to 226let any user create more users, right?) or to write an entry to a 227changelog. If the user has the C<is_superuser> flag set, things still 228get read from the database, but the user can walk around any and all 229ACL checks. Think "Neo" from the Matrix. The superuser can walk 230through walls, stop bullets and so on. 231 232 233=cut 234 235=head2 is_bootstrap_user 236 237When your system is first getting going, you can't assume 238B<anything>. There probably aren't any rights in the system to 239check. A user with the L</is_bootstrap_user> flag set is a 240self-reliant superuser. Nothing is read from the database, no ACLs are 241checked. You probably never need to do anything with bootstrap users. 242 243=cut 244 245=head2 current_user_can ACTION 246 247For a current user object, the current user can always C<read>, but 248never write or do anything else. 249 250=cut 251 252# XXX Is this actually used? 253sub current_user_can { 254 my $self = shift; 255 my $action = shift; 256 return (1) if $action eq 'read'; 257 return (0); 258} 259 260=head2 jifty_serialize_format 261 262Serializes as the user_object. 263 264=cut 265 266sub jifty_serialize_format { 267 my $self = shift; 268 return {} if !$self->user_object; 269 return $self->user_object->jifty_serialize_format(@_); 270} 271 272=head1 SEE ALSO 273 274L<Jifty::Object>, L<Jifty::Plugin::User> 275 276=head1 LICENSE 277 278Jifty is Copyright 2005-2010 Best Practical Solutions, LLC. 279Jifty is distributed under the same terms as Perl itself. 280 281=cut 282 2831; 284