From: Jess Robinson Date: Wed, 16 May 2012 15:26:01 +0000 (+0000) Subject: Merge UserStore and Directory as we had fun assigning users to traditions otherwise. X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=cf7e4e7bbd96ff1cd20f79032af32e85f968d0c8;p=scpubgit%2Fstemmatology.git Merge UserStore and Directory as we had fun assigning users to traditions otherwise. Fixup tests and admin script to match change. Now we can assign users to traditions. --- diff --git a/lib/Text/Tradition.pm b/lib/Text/Tradition.pm index ef50df6..97971a0 100644 --- a/lib/Text/Tradition.pm +++ b/lib/Text/Tradition.pm @@ -6,6 +6,7 @@ use Moose; use Text::Tradition::Collation; use Text::Tradition::Stemma; use Text::Tradition::Witness; +use Text::Tradition::User; use vars qw( $VERSION ); $VERSION = "0.5"; diff --git a/lib/Text/Tradition/Directory.pm b/lib/Text/Tradition/Directory.pm index 2a1bffa..c6eafc0 100644 --- a/lib/Text/Tradition/Directory.pm +++ b/lib/Text/Tradition/Directory.pm @@ -10,6 +10,10 @@ use KiokuDB::TypeMap; use KiokuDB::TypeMap::Entry::Naive; use Text::Tradition::Error; +## users +use KiokuX::User::Util qw(crypt_password); +use Text::Tradition::User; + extends 'KiokuX::Model'; =head1 NAME @@ -195,7 +199,10 @@ before [ qw/ store update insert delete / ] => sub { my $self = shift; my @nontrad; foreach my $obj ( @_ ) { - if( ref( $obj ) && ref( $obj ) ne 'Text::Tradition' ) { +# if( ref( $obj ) && ref( $obj ) ne 'Text::Tradition' ) { + + if( ref( $obj ) && ref( $obj ) ne 'Text::Tradition' + && ref ($obj) ne 'Text::Tradition::User' ) { # Is it an id => Tradition hash? if( ref( $obj ) eq 'HASH' && keys( %$obj ) == 1 ) { my( $k ) = keys %$obj; @@ -277,6 +284,258 @@ sub throw { ); } +=head1 NAME + +Text::Tradition::UserStore - KiokuDB storage management for Users + +=head1 SYNOPSIS + + my $userstore = Text::Tradition::UserStore->new(dsn => 'dbi:SQLite:foo.db'); + my $newuser = $userstore->add_user({ username => 'fred', + password => 'somepassword' }); + + my $fetchuser = $userstore->find_user({ username => 'fred' }); + if($fetchuser->check_password('somepassword')) { + ## login user or .. whatever + } + + my $user = $userstore->deactivate_user({ username => 'fred' }); + if(!$user->active) { + ## shouldnt be able to login etc + } + +=head1 DESCRIPTION + +A L for managing the storage and creation of +L objects. Subclass or replace this module in +order to use a different source for stemmaweb users. + +=head2 ATTRIBUTES + +=head3 dsn + +Inherited from KiokuX::Model - dsn for the data store we are using. + +=head3 MIN_PASS_LEN + +Constant for the minimum password length when validating passwords, +defaults to "8". + +=cut + +has MIN_PASS_LEN => ( is => 'ro', isa => 'Num', default => sub { 8 } ); + +# has 'directory' => ( +# is => 'rw', +# isa => 'KiokuX::Model', +# handles => [] +# ); + +## TODO: Some of these methods should probably optionally take $user objects +## instead of hashrefs. + +## It also occurs to me that all these methods don't need to be named +## XX_user, but leaving that way for now incase we merge this code +## into ::Directory for one-store. + +## To die or not to die, on error, this is the question. + +=head2 METHODS + +=head3 add_user + +Takes a hashref of C, C. + +Create a new user object, store in the KiokuDB backend, and return it. + +=cut + +sub add_user { + my ($self, $userinfo) = @_; + my $username = $userinfo->{url} || $userinfo->{username}; + my $password = $userinfo->{password}; + + return unless ($username =~ /^https?:/ + || ($username && $self->validate_password($password))) ; + + my $user = Text::Tradition::User->new( + id => $username, + password => ($password ? crypt_password($password) : ''), + ); + + my $scope = $self->new_scope; + $self->store($user->kiokudb_object_id, $user); + + return $user; +} + +sub create_user { + my $self = shift; + return $self->add_user(@_); +} + +=head3 find_user + +Takes a hashref of C, optionally C. + +Fetches the user object for the given username and returns it. + +=cut + +sub find_user { + my ($self, $userinfo) = @_; + ## url or display? + # 'display' => 'castaway.myopenid.com', + # 'url' => 'http://castaway.myopenid.com/', + my $username = $userinfo->{url} || $userinfo->{username}; + + return $self->lookup(Text::Tradition::User->id_for_user($username)); + +} + +=head3 modify_user + +Takes a hashref of C and C (same as add_user). + +Retrieves the user, and updates it with the new information. Username +changing is not currently supported. + +Returns the updated user object, or undef if not found. + +=cut + +sub modify_user { + my ($self, $userinfo) = @_; + my $username = $userinfo->{username}; + my $password = $userinfo->{password}; + + return unless $username && $self->validate_password($password); + + my $scope = $self->new_scope; + my $user = $self->find_user({ username => $username }); + return unless $user; + + $user->password(crypt_password($password)); + + $self->update($user); + + return $user; +} + +=head3 deactivate_user + +Takes a hashref of C. + +Sets the users C flag to false (0), and sets all traditions +assigned to them to non-public, updates the storage and returns the +deactivated user. + +Returns undef if user not found. + +=cut + +sub deactivate_user { + my ($self, $userinfo) = @_; + my $username = $userinfo->{username}; + + return if !$username; + + my $user = $self->find_user({ username => $username }); + return if !$user; + + $user->active(0); + foreach my $tradition (@{ $user->traditions }) { + ## Not implemented yet + # $tradition->public(0); + } + my $scope = $self->new_scope; + + ## Should we be using Text::Tradition::Directory also? + $self->update(@{ $user->traditions }); + + $self->update($user); + + return $user; +} + +=head3 reactivate_user + +Takes a hashref of C. + +Returns the user object if already activated. Activates (sets the +active flag to true (1)), updates the storage and returns the user. + +Returns undef if the user is not found. + +=cut + +sub reactivate_user { + my ($self, $userinfo) = @_; + my $username = $userinfo->{username}; + + return if !$username; + + my $scope = $self->new_scope; + my $user = $self->find_user({ username => $username }); + return if !$user; + + return $user if $user->active; + + $user->active(1); + $self->update($user); + + return $user; +} + +=head3 delete_user + +CAUTION: Delets actual data! + +Takes a hashref of C. + +Returns undef if the user doesn't exist. + +Removes the user from the store and returns 1. + +=cut + +sub delete_user { + my ($self, $userinfo) = @_; + my $username = $userinfo->{username}; + + return if !$username; + + my $scope = $self->new_scope; + my $user = $self->find_user({ username => $username }); + return if !$user; + + ## Should we be using Text::Tradition::Directory for this bit? + $self->delete( @{ $user->traditions }); + + ## Poof, gone. + $self->delete($user); + + return 1; +} + +=head3 validate_password + +Takes a password string. Returns true if it is longer than +L, false otherwise. + +Used internally by L. + +=cut + +sub validate_password { + my ($self, $password) = @_; + + return if !$password; + return if length($password) < $self->MIN_PASS_LEN; + + return 1; +} + 1; =head1 LICENSE diff --git a/lib/Text/Tradition/UserStore.pm b/lib/Text/Tradition/UserStore.pm index 783bdc8..4927611 100644 --- a/lib/Text/Tradition/UserStore.pm +++ b/lib/Text/Tradition/UserStore.pm @@ -6,10 +6,10 @@ use warnings; use Moose; use KiokuX::User::Util qw(crypt_password); -extends 'KiokuX::Model'; +extends 'Text::Tradition::Directory'; +# extends 'KiokuX::Model'; use Text::Tradition::User; -# use Text::Tradition::Directory; =head1 NAME diff --git a/script/admin_users.pl b/script/admin_users.pl index f407917..59e326a 100644 --- a/script/admin_users.pl +++ b/script/admin_users.pl @@ -10,10 +10,8 @@ use Getopt::Long; use ExtUtils::MakeMaker(); use lib 'lib'; -use Text::Tradition::UserStore; use Text::Tradition::Directory; - my ($dsn, $command) = ('dbi:SQLite:dbname=db/traditions.db', 'add', undef); my ($username, $password, $tradition_id); @@ -35,7 +33,9 @@ if(!$username) { usage(); } -my $userstore = Text::Tradition::UserStore->new( dsn => $dsn); +# my $userstore = Text::Tradition::UserStore->new( dsn => $dsn); +my $userstore = Text::Tradition::Directory->new( dsn => $dsn); +my $new_scope = $userstore->new_scope; given ($command) { when ('add') { @@ -53,9 +53,15 @@ given ($command) { } when ('modify') { - if(!$tradition_id || (!$password || !$userstore->validate_password($password))) { + if(!$tradition_id && !$password) { print "Can't modify a user without a valid password or a tradition\n\n"; usage(); + break; + } + if( $password && !$userstore->validate_password($password)) { + print "Can't modify a user without a valid password\n\n"; + usage(); + break; } if($password) { my $user = $userstore->modify_user({ username => $username, @@ -66,17 +72,13 @@ given ($command) { print "OK.\n"; } } elsif($tradition_id) { - my $directory = Text::Tradition::Directory->new( - dsn => $dsn, - ); - my $new_scope = $directory->new_scope; - my $tradition = $directory->tradition($tradition_id); + my $tradition = $userstore->tradition($tradition_id); my $user = $userstore->find_user({ username => $username }); if(!$tradition || !$user) { print "Can't find one of '$username' or '$tradition_id' in the database!\n"; } else { $user->add_tradition($tradition); - $directory->update($tradition); + $userstore->update($tradition); $userstore->update($user); print "OK.\n"; } @@ -135,6 +137,8 @@ admin_users.pl - add / modify / etc users admin_user.pl -c modify -u jimbob -p "jimsnewpassword" + admin_user.pl -c modify -u jimbob -t "mytradition" + admin_user.pl -c delete -u jimbob =head1 OPTIONS @@ -153,4 +157,8 @@ The username of the new user or user to change. The new password or password to change. +=item -t | --tradition + +A Text::Tradition id or name which will be assigned to the user given. + =back diff --git a/t/text_tradition_user.t b/t/text_tradition_user.t index 38773a0..2645929 100644 --- a/t/text_tradition_user.t +++ b/t/text_tradition_user.t @@ -4,17 +4,16 @@ use strict; use warnings; use Test::More 'no_plan'; -# use KiokuX::Model; use File::Temp; -use_ok('Text::Tradition::UserStore'); +use_ok('Text::Tradition::Directory'); my $fh = File::Temp->new(); my $file = $fh->filename; $fh->close; my $dsn = "dbi:SQLite:dbname=$file"; -my $user_store = Text::Tradition::UserStore->new('dsn' => $dsn, +my $user_store = Text::Tradition::Directory->new('dsn' => $dsn, 'extra_args' => { 'create' => 1 } ); ## passwords