Files
Mozilla/mozilla/webtools/PLIF/PLIF/Service/User.pm

408 lines
14 KiB
Perl

# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*-
#
# This file is MPL/GPL dual-licensed under the following terms:
#
# The contents of this file are subject to the Mozilla Public License
# Version 1.1 (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS"
# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
# the License for the specific language governing rights and
# limitations under the License.
#
# The Original Code is PLIF 1.0.
# The Initial Developer of the Original Code is Ian Hickson.
#
# Alternatively, the contents of this file may be used under the terms
# of the GNU General Public License Version 2 or later (the "GPL"), in
# which case the provisions of the GPL are applicable instead of those
# above. If you wish to allow use of your version of this file only
# under the terms of the GPL and not to allow others to use your
# version of this file under the MPL, indicate your decision by
# deleting the provisions above and replace them with the notice and
# other provisions required by the GPL. If you do not delete the
# provisions above, a recipient may use your version of this file
# under either the MPL or the GPL.
package PLIF::Service::User;
use strict;
use vars qw(@ISA);
use PLIF::Service::Session;
@ISA = qw(PLIF::Service::Session);
1;
# This class implements an object and its associated factory service.
# Compare this with the UserFieldFactory class which implements a
# factory service only, and the various UserField descendant classes
# which implement Service Instances.
# XXX It would be interesting to implement crack detection (time since
# last incorrect login, number of login attempts performed with time
# since last incorrect login < a global delta, address changing
# timeout, etc).
sub provides {
my $class = shift;
my($service) = @_;
return ($service eq 'user.factory' or $class->SUPER::provides($service));
}
sub getUserByCredentials {
my $self = shift;
my($app, $username, $password) = @_;
my $object = $self->getUserByUsername($app, $username);
if (defined($object) and ($object->checkPassword($password))) {
return $object;
} else {
return undef;
}
}
sub getUserByUsername {
my $self = shift;
my($app, $username) = @_;
my(@data) = $app->getService('dataSource.user')->getUserByUsername($app, $username);
if (@data) {
return $self->objectCreate($app, @data);
} else {
return undef;
}
}
sub getUserByContactDetails {
my $self = shift;
my($app, $contactName, $address) = @_;
my(@data) = $app->getService('dataSource.user')->getUserByContactDetails($app, $contactName, $address);
if (@data) {
return $self->objectCreate($app, @data);
} else {
return undef;
}
}
sub getUserByID {
my $self = shift;
my($app, $userID) = @_;
my(@data) = $app->getService('dataSource.user')->getUserByID($app, $userID);
if (@data) {
return $self->objectCreate($app, @data);
} else {
return undef;
}
}
sub getNewUser {
my $self = shift;
my($app, $password) = @_;
return $self->objectCreate($app, undef, 0, $password, '', {}, [], []);
}
sub objectProvides {
my $class = shift;
my($service) = @_;
return ($service eq 'user' or $class->SUPER::objectProvides($service));
}
sub objectInit {
my $self = shift;
my($app, $userID, $mode, $password, $adminMessage, $fields, $groups, $rights) = @_;
$self->{'_DIRTY'} = {}; # make sure propertySet is happy
$self->SUPER::objectInit(@_);
$self->userID($userID);
$self->mode($mode); # 0=active, 1=disabled XXX need a way to make this extensible
$self->password($password);
$self->adminMessage($adminMessage);
$self->fields({});
$self->fieldsByID({});
# don't forget to update the 'hash' function if you add more properties/field whatever you want to call them
my $fieldFactory = $app->getService('user.fieldFactory');
foreach my $fieldID (keys(%$fields)) {
$self->insertField($fieldFactory->createFieldByID($app, $self, $fieldID, $fields->{$fieldID}));
}
# $groups is an array of arrays containing groupID, groupName, user level in group
my $groupsByID = {};
my $groupsByName = {};
foreach my $group (@$groups) {
$groupsByID->{$group->[0]} = {'name' => $group->[1], 'level' => $group->[2], }; # id => name, level
$groupsByName->{$group->[1]} = {'groupID' => $group->[0], 'level' => $group->[2], }; # name => id, level
}
$self->groupsByID($groupsByID); # authoritative version
$self->originalGroupsByID({%{$groupsByID}}); # a backup used to make a comparison when saving the groups
$self->groupsByName($groupsByName); # helpful version for output purposes only
# rights
$self->rights({ map {$_ => 1} @$rights }); # map a list of strings into a hash for easy access
$self->{'_DIRTY'}->{'properties'} = not(defined($userID));
}
sub hasRight {
my $self = shift;
my($right) = @_;
return (defined($self->rights->{$right}) or $self->levelInGroup(1)); # group 1 is a magical group
}
sub hasField {
my $self = shift;
my($category, $name) = @_;
if (defined($self->fields->{$category})) {
return $self->fields->{$category}->{$name};
}
return undef;
}
# returns a field *even if it does not exist yet*
# -- so if you want to add a field by name, you do:
# $user->getField('category', 'name')->data($myData);
sub getField {
my $self = shift;
my($category, $name) = @_;
my $field = $self->hasField($category, $name);
if (not defined($field)) {
$field = $self->insertField($self->app->getService('user.fieldFactory')->createFieldByName($self->app, $self, $category, $name));
}
return $field;
}
# returns a field *even if it does not exist yet*
# -- so if you want to add a field by ID, you do:
# $user->getField($fieldID)->data($myData);
sub getFieldByID {
my $self = shift;
my($ID) = @_;
my $field = $self->fieldsByID($ID);
if (not defined($field)) {
$field = $self->insertField($self->app->getService('user.fieldFactory')->createFieldByID($self->app, $self, $ID));
}
return $field;
}
sub getAddress {
my $self = shift;
my($protocol) = @_;
my $field = $self->hasField('contact', $protocol);
if (defined($field)) {
return $field->address;
} else {
return undef;
}
}
sub addFieldChange {
my $self = shift;
my($field, $newData, $password, $type) = @_;
$field->prepareChange($newData);
return $self->app->getService('dataSource.user')->setUserFieldChange($self->app, $self->userID, $field->fieldID, $newData, $password, $type);
}
sub performFieldChange {
my $self = shift;
my($changeID, $candidatePassword, $minTime) = @_;
my $dataSource = $self->app->getService('dataSource.user');
my($userID, $fieldID, $newData, $password, $createTime, $type) = $dataSource->getUserFieldChangeFromChangeID($self->app, $changeID);
# check for valid change
if (($userID != $self->userID) or # wrong change ID
(not $self->app->getService('service.password')->checkPassword($candidatePassword, $password)) or # wrong password
($createTime < $minTime)) { # expired change
return 0;
}
# perform the change
$self->getFieldByID($fieldID)->data($newData);
# remove the change from the list of pending changes
if ($type == 1) { # XXX HARDCODED CONSTANT ALERT
# this is an override change
# remove all pending changes for this field (including this one)
$dataSource->removeUserFieldChangesByUserIDAndFieldID($self->app, $userID, $fieldID);
} else {
# this is a normal change
# remove just this change
$dataSource->removeUserFieldChangesByChangeID($self->app, $changeID);
}
return 1;
}
# a convenience method for either setting a user setting from a new
# value or getting the user's prefs
sub setting {
my $self = shift;
my($variable, $setting) = @_;
$self->assert(ref($variable) eq 'SCALAR', 1, 'Internal Error: User object was expecting a scalar ref for setting() but didn\'t get one');
if (defined($$variable)) {
$self->getField('setting', $setting)->data($$variable);
} else {
my $field = $self->hasField('setting', $setting);
if (defined($field)) {
$$variable = $field->data;
}
}
}
sub hash {
my $self = shift;
my $result = $self->SUPER::hash();
$result->{'userID'} = $self->userID,
$result->{'mode'} = $self->mode,
$result->{'adminMessage'} = $self->adminMessage,
$result->{'groupsByID'} = $self->groupsByID;
$result->{'groupsByName'} = $self->groupsByName;
$result->{'rights'} = [keys(%{$self->rights})];
if ($self->levelInGroup(1)) {
# has all rights
$result->{'right'} = {};
foreach my $right (@{$self->app->getService('dataSource.user')->getAllRights($self->app)}) {
$result->{'right'}->{$right} = 1;
}
} else {
$result->{'right'} = $self->rights;
}
$result->{'fields'} = {};
foreach my $field (values(%{$self->fieldsByID})) {
# XXX should we also pass the field metadata on? (e.g. typeData)
$result->{'fields'}->{$field->fieldID} = $field->hash; # (not an array btw: could have holes)
$result->{'fields'}->{$field->category.':'.$field->name} = $field->hash;
}
return $result;
}
sub checkPassword {
my $self = shift;
my($password) = @_;
return $self->app->getService('service.passwords')->checkPassword($self->password, $password);
}
sub checkLogin {
my $self = shift;
return ($self->mode == 0);
}
sub joinGroup {
my $self = shift;
my($groupID, $level) = @_;
if ($level > 0) {
my $groupName = $self->app->getService('dataSource.user')->getGroupName($self->app, $groupID);
$self->{'groupsByID'}->{$groupID} = {'name' => $groupName, 'level' => $level, };
$self->{'groupsByName'}->{$groupName} = {'groupID' => $groupID, 'level' => $level, };
$self->invalidateRights();
$self->{'_DIRTY'}->{'groups'} = 1;
} else {
$self->leaveGroup($groupID, $level);
}
}
sub leaveGroup {
my $self = shift;
my($groupID) = @_;
if (defined($self->{'groupsByID'}->{$groupID})) {
delete($self->{'groupsByName'}->{$self->{'groupsByID'}->{$groupID}->{'name'}});
delete($self->{'groupsByID'}->{$groupID});
$self->invalidateRights();
$self->{'_DIRTY'}->{'groups'} = 1;
}
}
sub levelInGroup {
my $self = shift;
my($groupID) = @_;
if (defined($self->{'groupsByID'}->{$groupID})) {
return $self->{'groupsByID'}->{$groupID}->{'level'};
} else {
return 0;
}
}
# internal routines
sub insertField {
my $self = shift;
my($field) = @_;
$self->fields->{$field->category}->{$field->name} = $field;
$self->fieldsByID->{$field->fieldID} = $field;
return $field;
}
sub invalidateRights {
my $self = shift;
my $rights = $self->app->getService('dataSource.user')->getRightsForGroup($self->app, keys(%{$self->{'groupsByID'}}));
$self->rights({ map {$_ => 1} @$rights }); # map a list of strings into a hash for easy access
# don't set a dirty flag, because rights are merely a convenient
# cached expansion of the rights data. Changing this externally
# makes no sense -- what rights one has is dependent on what
# groups one is in, and changing the rights won't magically change
# what groups you are in (how could it).
}
sub propertySet {
my $self = shift;
my($name, $value) = @_;
# check that we're not doing silly things like changing the user's ID
my $hadUndefinedID = (($name eq 'userID') and
($self->propertyExists($name)) and
(not defined($self->propertyGet($name))));
my $result = $self->SUPER::propertySet(@_);
if (($hadUndefinedID) and (defined($value))) {
# we've just aquired an ID, so propagate the change to all fields
foreach my $field (values(%{$self->fieldsByID})) {
$field->userID($value);
}
# and mark the groups as dirty too
$self->{'_DIRTY'}->{'groups'} = 1;
}
$self->{'_DIRTY'}->{'properties'} = 1;
return $result;
}
sub propertyGet {
my $self = shift;
my($name) = @_;
if ($name eq 'groupsByID') {
return {%{$self->{'groupsByID'}}};
# Create new hash so that they can't edit ours. This ensures
# that they can't inadvertently bypass the DIRTY flagging by
# propertySet(), above. This does mean that internally we have
# to access $self->{'groupsByID'} instead of $self->groupsByID.
} else {
# we don't bother looking at $self->rights or
# $self->groupsByName, but any changes made to those won't be
# saved anyway.
return $self->SUPER::propertyGet(@_);
}
}
sub DESTROY {
my $self = shift;
if ($self->{'_DIRTY'}->{'properties'}) {
$self->writeProperties();
}
if ($self->{'_DIRTY'}->{'groups'}) {
$self->writeGroups();
}
$self->SUPER::DESTROY(@_);
}
sub writeProperties {
my $self = shift;
$self->userID($self->app->getService('dataSource.user')->setUser($self->app, $self->userID, $self->mode,
$self->password, $self->adminMessage,
$self->newFieldID, $self->newFieldValue, $self->newFieldKey));
}
sub writeGroups {
my $self = shift;
# compare the group lists before and after and see which got added or changed and which got removed
my $dataSource = $self->app->getService('dataSource.user');
foreach my $group (keys(%{$self->{'groupsByID'}})) {
if ((not defined($self->{'originalGroupsByID'}->{$group})) or
($self->{'groupsByID'}->{$group}->{'level'} != $self->{'originalGroupsByID'}->{$group}->{'level'})) {
$dataSource->addUserGroup($self->app, $self->userID, $group, $self->{'groupsByID'}->{$group}->{'level'});
}
}
foreach my $group (keys(%{$self->{'originalGroupsByID'}})) {
if (not defined($self->{'groupsByID'}->{$group})) {
$dataSource->removeUserGroup($self->app, $self->userID, $group);
}
}
}
# fields write themselves out