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