throw exceptions for bad usermod requests
[scpubgit/stemmatology.git] / lib / Text / Tradition / UserStore.pm
CommitLineData
2006bd3f 1package Text::Tradition::UserStore;
2
3use strict;
4use warnings;
5
6use Moose;
7use KiokuX::User::Util qw(crypt_password);
f7ff202c 8use Text::Tradition::Error;
2006bd3f 9
cf7e4e7b 10extends 'Text::Tradition::Directory';
11# extends 'KiokuX::Model';
d1ba091f 12
2006bd3f 13use Text::Tradition::User;
2006bd3f 14
1384ec2d 15=head1 NAME
16
17Text::Tradition::UserStore - KiokuDB storage management for Users
18
19=head1 SYNOPSIS
20
21 my $userstore = Text::Tradition::UserStore->new(dsn => 'dbi:SQLite:foo.db');
22 my $newuser = $userstore->add_user({ username => 'fred',
23 password => 'somepassword' });
24
25 my $fetchuser = $userstore->find_user({ username => 'fred' });
26 if($fetchuser->check_password('somepassword')) {
27 ## login user or .. whatever
28 }
29
30 my $user = $userstore->deactivate_user({ username => 'fred' });
31 if(!$user->active) {
32 ## shouldnt be able to login etc
33 }
34
35=head1 DESCRIPTION
36
37A L<KiokuX::Model> for managing the storage and creation of
38L<Text::Tradition::User> objects. Subclass or replace this module in
39order to use a different source for stemmaweb users.
40
41=head2 ATTRIBUTES
42
43=head3 dsn
44
45Inherited from KiokuX::Model - dsn for the data store we are using.
46
47=head3 MIN_PASS_LEN
48
49Constant for the minimum password length when validating passwords,
50defaults to "8".
51
52=cut
53
ef02228c 54has MIN_PASS_LEN => ( is => 'ro', isa => 'Num', default => sub { 8 } );
55
d1ba091f 56# has 'directory' => (
57# is => 'rw',
58# isa => 'KiokuX::Model',
59# handles => []
60# );
2006bd3f 61
570cf8ba 62## TODO: Some of these methods should probably optionally take $user objects
63## instead of hashrefs.
64
65## It also occurs to me that all these methods don't need to be named
66## XX_user, but leaving that way for now incase we merge this code
67## into ::Directory for one-store.
68
1384ec2d 69=head2 METHODS
70
71=head3 add_user
72
73Takes a hashref of C<username>, C<password>.
74
75Create a new user object, store in the KiokuDB backend, and return it.
76
77=cut
78
2006bd3f 79sub add_user {
ef02228c 80 my ($self, $userinfo) = @_;
63bedbf3 81 my $username = $userinfo->{url} || $userinfo->{username};
ef02228c 82 my $password = $userinfo->{password};
83
f7ff202c 84 throw( "No username given" ) unless $username;
85 throw( "Invalid password - too short?" )
86 unless ( $self->validate_password($password) || $username =~ /^https?:/ );
87
2006bd3f 88 my $user = Text::Tradition::User->new(
89 id => $username,
63bedbf3 90 password => ($password ? crypt_password($password) : ''),
2006bd3f 91 );
92
d1ba091f 93 my $scope = $self->new_scope;
94 $self->store($user->kiokudb_object_id, $user);
2006bd3f 95
96 return $user;
97}
98
63bedbf3 99sub create_user {
100 my $self = shift;
101 return $self->add_user(@_);
102}
103
1384ec2d 104=head3 find_user
105
031a15f4 106Takes a hashref of C<username>, optionally C<openid_identifier>.
1384ec2d 107
108Fetches the user object for the given username and returns it.
109
110=cut
111
2006bd3f 112sub find_user {
d1ba091f 113 my ($self, $userinfo) = @_;
031a15f4 114 ## url or display?
115 # 'display' => 'castaway.myopenid.com',
116 # 'url' => 'http://castaway.myopenid.com/',
117 my $username = $userinfo->{url} || $userinfo->{username};
2006bd3f 118
570cf8ba 119 my $scope = $self->new_scope;
d1ba091f 120 return $self->lookup(Text::Tradition::User->id_for_user($username));
2006bd3f 121
122}
123
1384ec2d 124=head3 modify_user
125
126Takes a hashref of C<username> and C<password> (same as add_user).
127
128Retrieves the user, and updates it with the new information. Username
f7ff202c 129changing is not currently supported. Returns the updated user object.
130Throws an error if user not found.
1384ec2d 131
132=cut
133
ef02228c 134sub modify_user {
135 my ($self, $userinfo) = @_;
136 my $username = $userinfo->{username};
137 my $password = $userinfo->{password};
138
f7ff202c 139 throw( "Missing username or bad password" )
140 unless $username && $self->validate_password($password);
ef02228c 141
142 my $user = $self->find_user({ username => $username });
f7ff202c 143 throw( "Could not find user $username" ) unless $user;
ef02228c 144
145 my $scope = $self->new_scope;
146 $user->password(crypt_password($password));
147
148 $self->update($user);
149
150 return $user;
151}
152
1384ec2d 153=head3 deactivate_user
154
155Takes a hashref of C<username>.
156
157Sets the users C<active> flag to false (0), and sets all traditions
158assigned to them to non-public, updates the storage and returns the
159deactivated user.
160
f7ff202c 161Throws an error if user not found.
1384ec2d 162
163=cut
164
570cf8ba 165sub deactivate_user {
166 my ($self, $userinfo) = @_;
167 my $username = $userinfo->{username};
168
f7ff202c 169 throw( "Need to specify a username for deactivation" ) unless $username;
570cf8ba 170
171 my $user = $self->find_user({ username => $username });
f7ff202c 172 throw( "User $username not found" ) unless $user;
570cf8ba 173
174 $user->active(0);
175 foreach my $tradition (@{ $user->traditions }) {
176 ## Not implemented yet
177 # $tradition->public(0);
178 }
179 my $scope = $self->new_scope;
180
181 ## Should we be using Text::Tradition::Directory also?
182 $self->update(@{ $user->traditions });
183
184 $self->update($user);
185
186 return $user;
ef02228c 187}
188
1384ec2d 189=head3 reactivate_user
190
191Takes a hashref of C<username>.
192
193Returns the user object if already activated. Activates (sets the
194active flag to true (1)), updates the storage and returns the user.
195
f7ff202c 196Throws an error if the user is not found.
1384ec2d 197
198=cut
199
570cf8ba 200sub reactivate_user {
201 my ($self, $userinfo) = @_;
202 my $username = $userinfo->{username};
203
f7ff202c 204 throw( "Need to specify a username for reactivation" ) unless $username;
570cf8ba 205
206 my $user = $self->find_user({ username => $username });
f7ff202c 207 throw( "User $username not found" ) unless $user;
570cf8ba 208
209 return $user if $user->active;
210
211 $user->active(1);
212 my $scope = $self->new_scope;
213 $self->update($user);
214
215 return $user;
216}
217
1384ec2d 218=head3 delete_user
219
220CAUTION: Delets actual data!
221
222Takes a hashref of C<username>.
223
f7ff202c 224Throws an error if the user doesn't exist.
1384ec2d 225
226Removes the user from the store and returns 1.
227
228=cut
229
570cf8ba 230sub delete_user {
231 my ($self, $userinfo) = @_;
232 my $username = $userinfo->{username};
233
f7ff202c 234 throw( "Need to specify a username for deletion" ) unless $username;
570cf8ba 235
236 my $user = $self->find_user({ username => $username });
f7ff202c 237 throw( "User $username not found" ) unless $user;
570cf8ba 238
239 my $scope = $self->new_scope;
240
241 ## Should we be using Text::Tradition::Directory for this bit?
242 $self->delete( @{ $user->traditions });
243
244 ## Poof, gone.
245 $self->delete($user);
246
247 return 1;
248}
ef02228c 249
1384ec2d 250=head3 validate_password
251
252Takes a password string. Returns true if it is longer than
253L</MIN_PASS_LEN>, false otherwise.
254
255Used internally by L</add_user>.
256
257=cut
258
ef02228c 259sub validate_password {
260 my ($self, $password) = @_;
261
262 return if !$password;
263 return if length($password) < $self->MIN_PASS_LEN;
264
265 return 1;
266}
267
f7ff202c 268sub throw {
269 Text::Tradition::Error->throw(
270 'ident' => 'UserStore error',
271 'message' => $_[0],
272 );
273}
274
2006bd3f 2751;