Two significant user authentication changes:

Bug 329250 - User permission groups. Creates several layers of admin
groups, including super-administrators, test run/test day administrators,
and product administrators, and restricts access to administrative
functions according to user group levels. Also adds auth tools to search
for users by group and to grant/revoke group permissions.

Added hooks for testcases to belong to security groups (much like
Bugzilla's group system) for future use.

Bug 314928 - Forgot Password feature. Allows users who have forgotten
their passwords to change them without intervention from the QA team.
Password change requests are authenticated by an email to the user and a
link they must follow to confirm their identity. Also adds Litmus::Mailer,
with general support for sending email from within Litmus for future email
features.

Also reinstated Memoization in a mod_perl-aware way for a few common
functions.


git-svn-id: svn://10.0.0.236/trunk@227557 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
zach%zachlipton.com 2007-06-05 22:29:44 +00:00
parent e2f8d0ce1b
commit 3c1dc52fd9
43 changed files with 1508 additions and 110 deletions

View File

@ -2,7 +2,7 @@
== Required Perl Modules ==
Apache::DBI
Apache::DBI - if mod_perl is being used
CGI
Class::DBI
Class::DBI::mysql
@ -24,13 +24,31 @@ Time::Piece::MySQL
Time::Seconds
XML::XPath
Also, note that 'sendmail' (or a mailer with a compatible command-line
interface) is required.
== Setting up the database ==
Run populatedb.pl. This will create a template configuration file, 'localconfig,' that contains variables to hold your database configuration information. Edit the newly created 'localconfig' file with your database configuration. Once 'localconfig' is populated with your database information, run the populatedb.pl again to populate the initial products, locales, etc... There is no UI at present for doing this.
Run populatedb.pl. This will create a template configuration file,
'localconfig,' that contains variables to hold your database configuration
information. Edit the newly created 'localconfig' file with your database
configuration. Once 'localconfig' is populated with your database information,
run the populatedb.pl again to populate the initial products, locales, etc...
There is no UI at present for doing this.
Then just pop the whole thing into a directory where your web server can
get at it. Have fun!
To get yourself an administrator account, you'll need to load up Litmus in your
web browser and follow the link to create a new account. You'll have been
lucky enough to score user id number 1. Grant yourself admin rights by loading
up mysql, connecting to your Litmus database, and running the following command:
insert into user_group_map values ('1', '1');
From there, you can go into "Edit Users" in the web interface and grant
rights to any new users.
Note: After upgrading Litmus, it's a good idea to run populatedb.pl
again to pick up any schema changes that may have occured.

View File

@ -63,11 +63,17 @@ sub init() {
}
# Global Template object
our $template;
sub template() {
my $class = shift;
request_cache()->{template} ||= Litmus::Template->create();
return request_cache()->{template};
my $class = shift;
$template ||= Litmus::Template->create();
return $template;
}
#sub template() {
# my $class = shift;
# request_cache()->{template} ||= Litmus::Template->create();
# return request_cache()->{template};
#}
# Global CGI object
sub cgi() {

View File

@ -43,6 +43,9 @@ use Litmus::Error;
use Time::Piece;
use Time::Seconds;
use Litmus::DB::User;
use Litmus::DB::PasswordResets;
use Litmus::Memoize;
use Litmus::Mailer;
use CGI;
@ -114,7 +117,7 @@ sub requireLogin {
}
# Used by a CGI in much the same way as requireLogin() when the user must
# be an admin to proceed.
# be a superuser to proceed.
sub requireAdmin {
my $return_to = shift;
my $cgi = Litmus->cgi();
@ -127,6 +130,48 @@ sub requireAdmin {
return $user;
}
# similar to the above, but the user may be a run/day admin
sub requireRunDayAdmin {
my $return_to = shift;
my $cgi = Litmus->cgi();
my $user = requireLogin($return_to, 1);
if (!$user || !$user->isRunDayAdmin()) {
print $cgi->header();
basicError("You must be a Test Run/Test Day administrator to perform this function.");
}
return $user;
}
# similar to requireAdmin, but the user may be a product admin as well
sub requireProductAdmin {
my $return_to = shift;
my $product = shift; # optional
my $cgi = Litmus->cgi();
my $user = requireLogin($return_to, 1);
if (!$user || !$user->isInAdminGroup()) {
print $cgi->header();
basicError("You must be a Litmus administrator to perform this function.");
}
# superusers can do anything:
if ($user->isSuperUser()) {
return $user;
}
# otherwise, they must be an admin of $product if $product is specified:
if ($product) {
if ($user->isProductAdmin($product)) {
return $user;
} else {
print $cgi->header();
basicError("You must be an administrator of product ".
$product->name()." to perform this function.");
}
}
return $user;
}
# Returns the current Litmus::DB::Session object corresponding to the current
# logged-in user, or 0 if no valid session exists
sub getCurrentSession() {
@ -165,6 +210,109 @@ sub getCurrentUser() {
}
}
########################################
# Reset Password #
########################################
# Change the user's forgotten password. This is done in two phases, with an
# email confirmation to validate the user's identity before changing the
# password.
sub resetPassword {
my $user = shift;
# invalidate any pending password reset sessions:
my @resets = Litmus::DB::PasswordResets->search(user => $user);
foreach my $cur (@resets) {
$cur->session()->makeExpire();
$cur->delete();
}
my $session = makeSession($user, 1);
Litmus::DB::PasswordResets->create({user => $user, session => $session});
my $vars_mail = {
user => $user,
token => $session->sessioncookie(),
};
my $output;
Litmus->template()->process("auth/passwordreset/message.mail.tmpl",
$vars_mail, \$output, {POST_CHOMP => 0, PRE_CHOMP => 0}) ||
internalError(Litmus->template()->error());
# fix weird template nonsense
$output =~ s/^.*(To: )/To: /s;
Litmus::Mailer::sendMessage($output);
my $vars_html = {
title => "Reset Password",
return_to => "login.cgi",
};
print Litmus->cgi()->header();
Litmus->template()->process("auth/passwordreset/checkEmail.html.tmpl", $vars_html) ||
internalError(Litmus->template()->error());
exit;
}
# after the user follows the link in the email, they end up here to actually
# reset their password:
sub resetPasswordForm {
my $token = shift;
my $reset = validateToken($token);
my $vars = {
title => "Reset Password",
return_to => "login.cgi",
user => $reset->user(),
token => $token,
};
print Litmus->cgi()->header();
Litmus->template()->process("auth/passwordreset/resetForm.html.tmpl", $vars) ||
internalError(Litmus->template()->error());
}
# and after they complete the reset password from, they come here to
# actually change the password:
sub doResetPassword {
my $uid = shift;
my $token = shift;
my $password = shift;
my $reset = validateToken($token);
changePassword($reset->user(), $password);
my $session = makeSession($reset->user());
Litmus->cgi()->storeCookie(makeCookie($session));
$reset->session()->makeExpire();
$reset->delete();
}
sub validateToken {
my $token = shift;
my @sessions = Litmus::DB::Session->search(sessioncookie => $token);
my $session = $sessions[0];
if (!$session) {
invalidInputError("That authentication token is invalid. It may have
been copied incorrectly from the email, or have already been used.
Please check that the entire URL was copied correctly.");
}
if (! $session->isValid()) {
invalidInputError("That authentication token has expired. You'll need to
request a new password reset and try again.");
}
my @resets = Litmus::DB::PasswordResets->search(session => $session);
my $reset = $resets[0];
if (!$reset) {
invalidInputError("That authentication token is invalid. It does not
belong to a user that has requested a password reset.");
}
return $reset;
}
#
# ONLY NON-PUBLIC API BEYOND THIS POINT
@ -187,6 +335,22 @@ sub processLoginForm {
return; # we're done here
}
# the user just reset their password, so no need to do anything
if ($c->param('login_type') eq 'doResetPassword') {
return;
}
# check to see if they have forgotten their password:
if ($type eq "forgot_password") {
my $username = $c->param("email");
my @users = Litmus::DB::User->search(email => $username);
if (! $users[0]) {
loginError($c, "Invalid email address entered. Please try again");
}
resetPassword($users[0]);
return;
}
if ($type eq "litmus") {
my $username = $c->param("email");
my $password = $c->param("password");
@ -390,6 +554,7 @@ sub expireSessions {
# Given a userobj, process the login and return a session object
sub makeSession {
my $userobj = shift;
my $dontsetsession = shift;
my $c = Litmus->cgi();
my $expires = localtime() + ONE_DAY * $cookie_expire_days;
@ -401,7 +566,9 @@ sub makeSession {
sessioncookie => $sessioncookie,
expires => $expires});
Litmus->request_cache->{'curSession'} = $session;
unless ($dontsetsession) {
Litmus->request_cache->{'curSession'} = $session;
}
return $session;
}
@ -530,16 +697,12 @@ sub getCookie() {
return getCurrentUser();
}
# trusted means "is a super user"
sub istrusted($) {
my $userobj = shift;
return 0 if (!$userobj);
if ($userobj->istrusted()) {
return 1;
} else {
return 0;
}
return $userobj->isSuperUser();
}
sub canEdit($) {

View File

@ -0,0 +1,53 @@
# -*- mode: cperl; c-basic-offset: 8; indent-tabs-mode: nil; -*-
=head1 COPYRIGHT
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1
#
# 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 Litmus.
#
# The Initial Developer of the Original Code is
# the Mozilla Corporation.
# Portions created by the Initial Developer are Copyright (C) 2006
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Chris Cooper <ccooper@deadsquid.com>
# Zach Lipton <zach@zachlipton.com>
#
# ***** END LICENSE BLOCK *****
=cut
package Litmus::DB::GroupProductMap;
use strict;
use Litmus::Config;
use base 'Litmus::DBI';
Litmus::DB::GroupProductMap->table('group_product_map');
Litmus::DB::GroupProductMap->columns(All => qw/group_id product_id/);
Litmus::DB::GroupProductMap->columns(TEMP => qw//);
Litmus::DB::GroupProductMap->column_alias("group_id", "group");
Litmus::DB::GroupProductMap->column_alias("product_id", "product");
Litmus::DB::GroupProductMap->has_a(group => "Litmus::DB::SecurityGroup");
Litmus::DB::GroupProductMap->has_a(product => "Litmus::DB::Product");
1;

View File

@ -0,0 +1,53 @@
# -*- mode: cperl; c-basic-offset: 8; indent-tabs-mode: nil; -*-
=head1 COPYRIGHT
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1
#
# 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 Litmus.
#
# The Initial Developer of the Original Code is
# the Mozilla Corporation.
# Portions created by the Initial Developer are Copyright (C) 2006
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Chris Cooper <ccooper@deadsquid.com>
# Zach Lipton <zach@zachlipton.com>
#
# ***** END LICENSE BLOCK *****
=cut
package Litmus::DB::PasswordResets;
use strict;
use Litmus::Config;
use base 'Litmus::DBI';
Litmus::DB::PasswordResets->table('password_resets');
Litmus::DB::PasswordResets->columns(All => qw/user_id session_id/);
Litmus::DB::PasswordResets->columns(TEMP => qw//);
Litmus::DB::PasswordResets->column_alias("user_id", "user");
Litmus::DB::PasswordResets->column_alias("session_id", "session");
Litmus::DB::PasswordResets->has_a(user => "Litmus::DB::User");
Litmus::DB::PasswordResets->has_a(session => "Litmus::DB::Session");
1;

View File

@ -51,6 +51,7 @@ Litmus::DB::Product->has_many(testgroups => "Litmus::DB::Testgroup",
{ order_by => 'name' });
Litmus::DB::Product->has_many(branches => "Litmus::DB::Branch",
{ order_by => 'name' });
Litmus::DB::Product->has_many(groups => ["Litmus::DB::GroupProductMap" => 'group_id']);
__PACKAGE__->set_sql(ByPlatform => qq{
SELECT pr.*

View File

@ -0,0 +1,120 @@
# -*- mode: cperl; c-basic-offset: 8; indent-tabs-mode: nil; -*-
=head1 COPYRIGHT
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1
#
# 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 Litmus.
#
# The Initial Developer of the Original Code is
# the Mozilla Corporation.
# Portions created by the Initial Developer are Copyright (C) 2007
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Zach Lipton <zach@zachlipton.com>
#
# ***** END LICENSE BLOCK *****
=cut
package Litmus::DB::SecurityGroup;
use strict;
use Litmus::Config;
use Litmus::Memoize;
use base 'Litmus::DBI';
Litmus::DB::SecurityGroup->table('security_groups');
Litmus::DB::SecurityGroup->columns(All => qw/group_id name description grouptype isactive/);
Litmus::DB::SecurityGroup->columns(TEMP => qw//);
Litmus::DB::SecurityGroup->has_many(users => ["Litmus::DB::UserGroupMap" => 'user_id']);
Litmus::DB::SecurityGroup->has_many(products => ["Litmus::DB::GroupProductMap" => 'product_id']);
# group types:
# 1 - superuser
# 2 - test run and test day administrator
# 3 - product admin
# 4 - testcase security group
# create an initial set of groups for an upgraded installation
sub upgradeGroups {
# check to see if groups need to be created:
my @rows = Litmus::DB::SecurityGroup->search({grouptype => 1});
if ($rows[0]) {
return;
}
print "creating intial groups...";
# first, create a superuser group:
my $admingroup = Litmus::DB::SecurityGroup->create({
name => "Litmus Super Administrators",
description => "Global administrators of this Litmus installation",
grouptype => 1,
isactive => 1,
});
# add current gloabl admins to the superuser group
my @admins = Litmus::DB::User->search({is_admin_old => 1});
foreach my $cur (@admins) {
Litmus::DB::UserGroupMap->create({group => $admingroup, user=>$cur});
}
# create a test run/testday admin group
my $rundaygroup = Litmus::DB::SecurityGroup->create({
name => "Litmus Test Run/Test Day Administrators",
description => "Administrators of Litmus Test Runs and Test Days",
grouptype => 2,
isactive => 1,
});
# create product admin groups for all products
my @products = Litmus::DB::Product->retrieve_all();
foreach my $cur2 (@products) {
my $productgroup = Litmus::DB::SecurityGroup->create({
name => $cur2->name()." Administrators",
description => "Administrators of the ".$cur2->name()." product",
grouptype => 3,
isactive => 1,
});
Litmus::DB::GroupProductMap->create({group=>$productgroup, product=>$cur2});
}
}
# hack!
sub selected {
my $self = shift;
if ($self->{'selected'}) {
return 1;
}
return 0;
}
# this can persist across mod_perl requests:
memoize('getSuperUserGroup', persist=>1);
sub getSuperUserGroup {
my $self = shift;
my @rows = __PACKAGE__->search(name => "Litmus Super Administrators");
return $rows[0];
}
memoize('getRunDayGroup', persist=>1);
sub getRunDayGroup {
my $self = shift;
my @rows = __PACKAGE__->search(name => "Litmus Test Run/Test Day Administrators");
return $rows[0];
}
1;

View File

@ -139,7 +139,7 @@ sub coverage() {
$sql = "SELECT t.testcase_id, count(tr.testresult_id) AS num_results
FROM testcase_subgroups tsg JOIN testcases t ON (tsg.testcase_id=t.testcase_id) LEFT JOIN test_results tr ON (tr.testcase_id=t.testcase_id) JOIN opsyses o ON (tr.opsys_id=o.opsys_id)";
if ($trusted) {
$sql .= ", users u";
$sql .= ", users u, user_group_map ugm, security_groups sg";
}
$sql .= " WHERE tsg.subgroup_id=? AND tr.build_id=? AND tr.locale_abbrev=? AND o.platform_id=? AND o.opsys_id=?";
if ($community_only) {
@ -149,7 +149,8 @@ sub coverage() {
$sql .= " AND tr.user_id=" . $user->{'user_id'};
}
if ($trusted) {
$sql .= " AND tr.user_id=u.user_id AND u.is_admin=1";
$sql .= " AND tr.user_id=u.user_id AND u.user_id=ugm.user_id AND ugm.group_id=sd.group_id ";
$sql .= " AND (sd.grouptype=1 OR sd.grouptype=3)";
}
$sql .= " GROUP BY tr.testcase_id";

View File

@ -53,6 +53,14 @@ Litmus::DB::TestRun->has_a(product => "Litmus::DB::Product");
Litmus::DB::TestRun->has_a(branch => "Litmus::DB::Branch");
Litmus::DB::TestRun->has_a(author => "Litmus::DB::User");
Litmus::DB::TestRun->set_sql('daterange' => qq {
SELECT __ESSENTIAL__
FROM __TABLE__
WHERE
start_timestamp<= ? AND finish_timestamp>?
ORDER BY finish_timestamp ASC, product_id ASC, branch_id ASC, test_run_id ASC
});
#########################################################################
sub getCriteria() {
my $self = shift;
@ -313,8 +321,11 @@ sub coverage {
my $sql = "
SELECT COUNT(DISTINCT(tr.testcase_id))
FROM test_runs trun, test_run_testgroups truntg, testgroups tg, subgroup_testgroups sgtg, subgroups sg, testcase_subgroups tcsg, testcases tc, test_results tr, opsyses o, platforms pl, users u
WHERE
FROM test_runs trun, test_run_testgroups truntg, testgroups tg, subgroup_testgroups sgtg, subgroups sg, testcase_subgroups tcsg, testcases tc, test_results tr, opsyses o, platforms pl, users u";
if ($trusted) {
$sql .= ", user_group_map ugm, security_groups secgrps";
}
$sql .= " WHERE
trun.test_run_id=? AND
trun.test_run_id=truntg.test_run_id AND
truntg.testgroup_id=sgtg.testgroup_id AND
@ -343,7 +354,8 @@ WHERE
}
if ($trusted) {
$sql .= " AND u.is_admin=1";
$sql .= " AND tr.user_id=u.user_id AND u.user_id=ugm.user_id AND
ugm.group_id=sd.group_id AND (secgrps.grouptype=1 OR secgrps.grouptype=3)";
}
$sql .= $self->getCriteriaSql();
@ -373,8 +385,11 @@ sub getNumResultsByStatus {
my $sql = "
SELECT COUNT(DISTINCT(tr.testresult_id))
FROM test_runs trun, test_run_testgroups truntg, testgroups tg, subgroup_testgroups sgtg, subgroups sg, testcase_subgroups tcsg, testcases tc, test_results tr, opsyses o, platforms pl, users u
WHERE
FROM test_runs trun, test_run_testgroups truntg, testgroups tg, subgroup_testgroups sgtg, subgroups sg, testcase_subgroups tcsg, testcases tc, test_results tr, opsyses o, platforms pl, users u";
if ($trusted) {
$sql .= ", user_group_map ugm, security_groups secgrps";
}
$sql .= " WHERE
trun.test_run_id=? AND
trun.test_run_id=truntg.test_run_id AND
truntg.testgroup_id=sgtg.testgroup_id AND
@ -404,7 +419,8 @@ WHERE
}
if ($trusted) {
$sql .= " AND u.is_admin=1";
$sql .= " AND tr.user_id=u.user_id AND u.user_id=ugm.user_id AND
ugm.group_id=sd.group_id AND (secgrps.grouptype=1 OR secgrps.grouptype=3)";
}
$sql .= $self->getCriteriaSql();
@ -427,8 +443,11 @@ sub getNumResultsWithComments {
my $sql = "
SELECT COUNT(DISTINCT(tr.testresult_id))
FROM test_runs trun, test_run_testgroups trtg, testgroups tg, subgroup_testgroups sgtg, subgroups sg, testcase_subgroups tcsg, testcases tc, test_results tr INNER JOIN test_result_comments trc ON tr.testresult_id=trc.test_result_id, opsyses o, platforms pl, users u
WHERE
FROM test_runs trun, test_run_testgroups trtg, testgroups tg, subgroup_testgroups sgtg, subgroups sg, testcase_subgroups tcsg, testcases tc, test_results tr INNER JOIN test_result_comments trc ON tr.testresult_id=trc.test_result_id, opsyses o, platforms pl, users u ";
if ($trusted) {
$sql .= ", user_group_map ugm, security_groups secgrps";
}
$sql .= " WHERE
trun.test_run_id=? AND
trun.test_run_id=trtg.test_run_id AND
trtg.testgroup_id=sgtg.testgroup_id AND
@ -457,7 +476,8 @@ WHERE
}
if ($trusted) {
$sql .= " AND u.is_admin=1";
$sql .= " AND tr.user_id=u.user_id AND u.user_id=ugm.user_id AND
ugm.group_id=sd.group_id AND (secgrps.grouptype=1 OR secgrps.grouptype=3)";
}
$sql .= $self->getCriteriaSql();

View File

@ -232,7 +232,8 @@ sub coverage() {
$where .= " AND tr.user_id=" . quotemeta($user->{'user_id'});
}
if ($trusted) {
$where .= " AND u.is_admin=1";
$from .= ", user_group_map ugm, security_groups sg";
$where .= " AND tr.user_id=u.user_id AND u.user_id=ugm.user_id AND ugm.group_id=sd.group_id AND (sg.grouptype=1 OR sg.grouptype=3)";
}
my $sql = $select . $from . $where . $order_by;
#print $sql,"<br/>\n";

View File

@ -125,13 +125,16 @@ Litmus::DB::Testresult->set_sql(CompletedByUser => qq{
Litmus::DB::Testresult->set_sql(CompletedByTrusted => qq{
SELECT tr.*
FROM test_results tr, users u
FROM test_results tr, users u, users u,
user_group_map ugm, security_groups sg
WHERE tr.testcase_id=? AND
tr.build_id=? AND
tr.locale_abbrev=? AND
tr.opsys_id=? AND
tr.user_id=u.user_id AND
u.is_admin=1
AND tr.user_id=u.user_id AND u.user_id=ugm.user_id AND
ugm.group_id=sd.group_id AND
(sd.grouptype=1 OR sd.grouptype=3)
ORDER BY tr.submission_time DESC
});
@ -231,14 +234,22 @@ sub getTestResults($\@\@$) {
} elsif ($criterion->{'field'} eq 'trusted_only') {
if ($criterion->{'value'} ne 'all') {
if ($from !~ /users u/) {
$from .= ", users u";
$from .= ", users u"
}
$from .= ", user_group_map ugm, ";
$from .= "security_groups secgps, group_product_map gpm";
}
$where .= " AND u.user_id=tr.user_id AND u.is_admin=";
if ($criterion->{'value'} == 1 or $criterion->{'value'} eq 'on') {
$where .= '1';
$where .= qq{ AND u.user_id=tr.user_id AND (
u.user_id=ugm.user_id
AND ugm.group_id=secgps.group_id AND
secgps.grouptype=1) OR
(gpm.product_id=pr.product_id AND
gpm.group_id=secgps.group_id AND
secgps.grouptype=3)};
} else {
$where .= '0';
$where .= '(secgps.grouptype != 1 AND secgps.grouptype != 2
AND secgps.grouptype != 3)';
}
} elsif ($criterion->{'field'} eq 'vetted_only') {
if ($criterion->{'value'} ne 'all') {

View File

@ -34,19 +34,23 @@ package Litmus::DB::User;
use strict;
use Litmus::Config;
use Litmus::Memoize;
use base 'Litmus::DBI';
Litmus::DB::User->table('users');
Litmus::DB::User->columns(All => qw/user_id bugzilla_uid email password realname irc_nickname enabled is_admin authtoken/);
Litmus::DB::User->columns(TEMP => qw/num_results/);
Litmus::DB::User->columns(All => qw/user_id bugzilla_uid email password realname irc_nickname enabled authtoken/);
Litmus::DB::User->columns(TEMP => qw/is_admin_old num_results/);
Litmus::DB::User->column_alias("isSuperUser", "istrusted");
Litmus::DB::User->column_alias("isSuperUser", "is_trusted");
Litmus::DB::User->column_alias("isSuperUser", "is_admin");
Litmus::DB::User->column_alias("is_trusted", "istrusted");
Litmus::DB::User->column_alias("is_admin", "is_trusted");
Litmus::DB::User->column_alias("email", "username");
Litmus::DB::User->has_many(test_results => "Litmus::DB::Testresult");
Litmus::DB::User->has_many(sessions => "Litmus::DB::Session");
Litmus::DB::User->has_many(groups => ["Litmus::DB::UserGroupMap" => 'group']);
# ZLL: only load BugzillaUser if Bugzilla Auth is actually enabled
if ($Litmus::Config::bugzilla_auth_enabled) {
@ -68,6 +72,63 @@ __PACKAGE__->set_sql(TopTesters => qq{
LIMIT 15
});
#########################################################################
# search for users by email, irc nick, realname, or group
sub search_full_text {
my $self = shift;
my $email = shift;
my $nick = shift;
my $realname = shift;
my @groups = shift;
my $dbh = Litmus::DBI->db_Main();
my @args;
my $sql = q{
SELECT DISTINCT users.user_id, users.email, users.irc_nickname
FROM users, user_group_map
WHERE
(
};
if ($email) {
$sql .= "users.email COLLATE latin1_general_ci like concat('%%',?,'%%') OR ";
push(@args, $email);
}
if ($nick) {
$sql .= "users.irc_nickname COLLATE latin1_general_ci like concat('%%',?,'%%') OR ";
push(@args, $nick);
}
if ($realname) {
$sql .= "users.realname COLLATE latin1_general_ci like concat('%%',?,'%%')";
push(@args, $realname);
}
# catch all case for no email, nick, or realname
if (! ($realname || $nick || $email)) {
$sql .= "1=1";
}
$sql .= ")";
if ($groups[0]) {
$sql .= " AND ((user_group_map.user_id = users.user_id) ";
foreach my $cur (@groups) {
$sql .= "AND user_group_map.group_id = ".$cur->group_id();
}
$sql .= ") ";
}
$sql .= "GROUP BY users.user_id ";
$sql .= "ORDER BY users.email ASC";
my $sth = $dbh->prepare($sql);
$sth->execute(@args);
return $self->sth_to_objects($sth);
}
# the COLLATE latin1_general_ci sillyness forces a case-insensitive match
# removed a LIMIT 300 to work around a mysql bug in the ancient version
# on rodan
@ -96,6 +157,7 @@ sub getRealPasswd {
}
#########################################################################
memoize('getDisplayName');
sub getDisplayName() {
my $self = shift;
@ -114,6 +176,96 @@ sub getDisplayName() {
return undef;
}
#########################################################################
# Group functions
#########################################################################
__PACKAGE__->set_sql(userInGroup => q{
SELECT DISTINCT users.user_id FROM __TABLE__, security_groups, user_group_map
WHERE
user_group_map.group_id = ? AND users.user_id = user_group_map.user_id
AND users.user_id= ?
});
__PACKAGE__->set_sql(userInAnyAdminGroup => q{
SELECT DISTINCT users.user_id FROM __TABLE__, security_groups, user_group_map
WHERE
(security_groups.grouptype=1 OR security_groups.grouptype=2
OR security_groups.grouptype=3) AND
users.user_id = user_group_map.user_id
AND users.user_id= ?
});
__PACKAGE__->set_sql(userInProductAdminGroup => q{
SELECT DISTINCT users.user_id FROM __TABLE__, security_groups,
user_group_map, group_product_map
WHERE security_groups.grouptype=3 AND
users.user_id = user_group_map.user_id AND
user_group_map.group_id=security_groups.group_id AND
users.user_id= ? AND
security_groups.group_id=group_product_map.group_id AND
group_product_map.product_id = ?
});
# returns true if the user is a member of $group, otherwise false
memoize('inGroup');
sub inGroup {
my $self = shift;
my $group = shift;
my @users = __PACKAGE__->search_userInGroup($group, $self);
if (@users) { return 1 }
return 0;
}
memoize('isSuperUser');
sub isSuperUser {
my $self = shift;
if ($self->inGroup(Litmus::DB::SecurityGroup->getSuperUserGroup)) {
return 1;
}
else {
return 0;
}
}
memoize('isRunDayAdmin');
sub isRunDayAdmin {
my $self = shift;
if ($self->inGroup(Litmus::DB::SecurityGroup->getRunDayGroup()) ||
$self->isSuperUser()) {
return 1;
}
else {
return 0;
}
}
# returns true if the user is a superuser or a member of any product admin group
# (to determine whether to show any admin controls)
memoize('isInAdminGroup');
sub isInAdminGroup {
my $self = shift;
my @rows = $self->search_userInAnyAdminGroup($self);
if (@rows) {
return 1;
}
return 0;
}
# returns true if the user is an admin of $product
memoize('isProductAdmin');
sub isProductAdmin {
my $self = shift;
my $product = shift;
my @users = $self->search_userInProductAdminGroup($self, $product);
if (@users) {
return 1;
} else {
return 0;
}
}
1;

View File

@ -0,0 +1,66 @@
# -*- mode: cperl; c-basic-offset: 8; indent-tabs-mode: nil; -*-
=head1 COPYRIGHT
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1
#
# 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 Litmus.
#
# The Initial Developer of the Original Code is
# the Mozilla Corporation.
# Portions created by the Initial Developer are Copyright (C) 2006
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Chris Cooper <ccooper@deadsquid.com>
# Zach Lipton <zach@zachlipton.com>
#
# ***** END LICENSE BLOCK *****
=cut
package Litmus::DB::UserGroupMap;
use strict;
use Litmus::Config;
use base 'Litmus::DBI';
Litmus::DB::UserGroupMap->table('user_group_map');
Litmus::DB::UserGroupMap->columns(All => qw/user_id group_id/);
Litmus::DB::UserGroupMap->columns(TEMP => qw//);
Litmus::DB::UserGroupMap->column_alias("user_id", "user");
Litmus::DB::UserGroupMap->column_alias("group_id", "group");
Litmus::DB::UserGroupMap->has_a(user => "Litmus::DB::User");
Litmus::DB::UserGroupMap->has_a(group => "Litmus::DB::SecurityGroup");
__PACKAGE__->set_sql(remove_map => q{
DELETE FROM __TABLE__ WHERE
user_id = ? AND group_id = ?
});
sub remove {
my $self = shift;
my $user_id = shift;
my $group_id = shift;
my $sth = __PACKAGE__->sql_remove_map;
$sth->execute($user_id, $group_id);
}
1;

View File

@ -36,7 +36,7 @@ require Apache::DBI;
use strict;
use warnings;
use Litmus::Config;
use Memoize;
use Litmus::Memoize;
use base 'Class::DBI::mysql';
@ -60,7 +60,7 @@ sub column_alias {
# here's where the actual work happens. We consult our alias list
# (as created by calls to column_alias()) and substitute the
# database column if we find a match
memoize('find_column');
memoize('find_column', persist=>1);
sub find_column {
my $self = shift;
my $wanted = shift;

View File

@ -295,14 +295,20 @@ sub getTestdays()
#########################################################################
sub getAuthors()
{
my $sql = "SELECT user_id, email FROM users WHERE is_admin=1 ORDER BY email";
my $sql = "SELECT users.user_id, users.email FROM users, user_group_map, security_groups
WHERE
users.user_id=user_group_map.user_id AND
user_group_map.group_id=security_groups.group_id AND
(security_groups.grouptype=1 OR security_groups.grouptype=2
OR security_groups.grouptype=3)
ORDER BY users.email";
return _getValues($sql);
}
#########################################################################
sub getTestRuns() {
my ($self, $enabled) = @_;
my $sql = "SELECT test_run_id, name FROM test_runs";
my $sql = "SELECT test_run_id, name, product_id FROM test_runs";
if ($enabled) {
$sql .= " WHERE enabled=1";
}

View File

@ -0,0 +1,63 @@
# -*- mode: cperl; c-basic-offset: 8; indent-tabs-mode: nil; -*-
=head1 COPYRIGHT
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1
#
# 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 Litmus.
#
# The Initial Developer of the Original Code is
# the Mozilla Corporation.
# Portions created by the Initial Developer are Copyright (C) 2007
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Zach Lipton <zach@zachlipton.com>
#
# ***** END LICENSE BLOCK *****
=cut
# Email management functions
package Litmus::Mailer;
use strict;
#use Litmus;
use Litmus::Error;
#use Litmus::Config;
use Email::MIME;
use Email::MIME::Modifier;
use Email::Send;
our @ISA = qw(Exporter);
@Litmus::Mailer::EXPORT = qw(
sendMessage
);
# cribbed in part from Bugzilla's MessageToMTA
sub sendMessage {
my $msg = shift;
local $ENV{PATH} = $Litmus::Config::sendmail_path;
open(MAIL, "| sendmail -t -oi") || Litmus::Error::basicError("Could not send email: $!");
print MAIL $msg || Litmus::Error::basicError("Could not send email: $!");
close(MAIL) || Litmus::Error::basicError("Could not send email: $!");
}
1;

View File

@ -0,0 +1,81 @@
# -*- mode: cperl; c-basic-offset: 8; indent-tabs-mode: nil; -*-
=head1 COPYRIGHT
# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1
#
# 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 Litmus.
#
# The Initial Developer of the Original Code is
# the Mozilla Corporation.
# Portions created by the Initial Developer are Copyright (C) 2006
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Zach Lipton <zach@zachlipton.com>
#
# ***** END LICENSE BLOCK *****
=cut
package Litmus::Memoize;
use strict;
use Exporter;
our @EXPORT = qw(memoize);
use Memoize ();
use base 'Memoize';
# Subclass of Memoize.pm that gives us control over when our data is
# flushed and ensures that cached data does not persist across mod_perl
# requests unless we really want it to
sub memoize {
my $fn = shift;
my %options = @_;
if ($ENV{MOD_PERL} && ! Apache->request()) {
return;
}
my $uppack = caller;
$options{INSTALL} = $uppack . '::' . $fn;
$fn = Memoize::_make_cref($fn, $uppack);
# if the persist flag is given, we store the memoized data normally
# and it will persist across mod_perl requests
if ($options{persist}) {
Memoize::memoize($fn, %options);
return;
}
# otherwise, we keep the cache in request_cache where it will get
# flushed when the request ends
my $cache = {};
if ($ENV{MOD_PERL}) {
$cache = Apache->request()->pnotes();
}
$cache->{'S'.$fn} = {};
$cache->{'L'.$fn} = {};
my $s_cache = $cache->{'S'.$fn};
my $l_cache = $cache->{'L'.$fn};
$options{SCALAR_CACHE} = [HASH => $s_cache];
$options{LIST_CACHE} = [HASH => $l_cache];
Memoize::memoize($fn, %options);
return;
}
1;

View File

@ -44,18 +44,37 @@ my $c = Litmus->cgi();
Litmus::Auth::requireLogin("edit_users.cgi");
# Only trusted users can edit other users.
my $cookie = Litmus::Auth::getCookie();
my $cookie = undef;
$cookie = Litmus::Auth::getCookie();
if (Litmus::Auth::istrusted($cookie)) {
if ($c->param('search_string')) {
if ($c->param('submit')) {
# search for users:
my @users = Litmus::DB::User->search_FullTextMatches(
# gather group membership bits:
my @groups = Litmus::DB::SecurityGroup->retrieve_all(order_by => "grouptype, group_id");
my @group_search;
my %checked;
foreach my $cur (@groups) {
if ($c->param("group_".$cur->group_id())) {
push(@group_search, $cur);
$checked{$cur->group_id()} = 1;
}
}
my @users = Litmus::DB::User->search_full_text(
$c->param('search_string'),
$c->param('search_string'),
$c->param('search_string'));
$c->param('search_string'),
@group_search);
my $vars = {
users => \@users,
search_string => $c->param('search_string'),
groups => \@groups,
checked => \%checked,
};
print $c->header();
Litmus->template()->process("admin/edit_users/search_results.html.tmpl", $vars) ||
@ -68,9 +87,11 @@ if (Litmus::Auth::istrusted($cookie)) {
if (! $user) {
invalidInputError("Invalid user ID: $uid");
}
my @groups = Litmus::DB::SecurityGroup->retrieve_all();
my $vars = {
user => $user,
};
groups => \@groups,
};
Litmus->template()->process("admin/edit_users/edit_user.html.tmpl", $vars) ||
internalError(Litmus->template()->error());
} elsif ($c->param('user_id')) {
@ -101,16 +122,23 @@ if (Litmus::Auth::istrusted($cookie)) {
$revoke_sessions = 1;
}
}
# Check to see whether we are changing the admin status of this user.
if ($c->param('is_admin')) {
$user->is_admin(1);
} else {
if ($user->is_admin) {
$user->is_admin(0);
$revoke_sessions = 1;
}
$user->authtoken($c->param('authtoken'));
# process changes to group permissions:
my @allgroups = Litmus::DB::SecurityGroup->retrieve_all();
foreach my $group (@allgroups) {
if ($c->param("group_".$group->group_id())) {
# we're blessing this user
Litmus::DB::UserGroupMap->find_or_create(user=>$user, group=>$group);
$revoke_sessions = 1;
} else {
# unblesing (if previously blessed) the user
Litmus::DB::UserGroupMap->remove($user, $group);
$revoke_sessions = 1;
}
}
$user->authtoken($c->param('authtoken'));
$user->update();
@ -121,13 +149,16 @@ if (Litmus::Auth::istrusted($cookie)) {
my $vars = {
user => $user,
onload => "toggleMessage('success','User information updated successfully.');"
onload => "toggleMessage('success','User information updated successfully.');",
groups => \@allgroups,
};
Litmus->template()->process("admin/edit_users/search_users.html.tmpl", $vars) ||
internalError(Litmus->template()->error());
} else {
# we're here for the first time, so display the search form
my @groups = Litmus::DB::SecurityGroup->retrieve_all();
my $vars = {
groups => \@groups,
};
print $c->header();
Litmus->template()->process("admin/edit_users/search_users.html.tmpl", $vars) ||

View File

@ -169,8 +169,8 @@ function populateTestRun(data) {
setAuthor(test_run.author_id.user_id);
var enabled_em = document.getElementById('enabled')
var enabled_display_em = document.getElementById('enabled_display')
var enabled_em = document.getElementById('enabled');
var enabled_display_em = document.getElementById('enabled_display');
if (test_run.enabled == 1) {
enabled_em.checked = true;
enabled_display_em.checked = true;

View File

@ -37,14 +37,31 @@ Litmus->init();
my $title = "Log in";
# allow the user to reset their forgotten password
my $c = Litmus->cgi();
if ($c->param('resetPassword')) {
Litmus::Auth::resetPasswordForm($c->param('resetPassword'));
exit;
}
if ($c->param('login_type') eq 'doResetPassword') {
# check that the two password fields are equal:
if ($c->param('password') ne $c->param('password_confirm')) {
invalidInputError("The 'password' and 'confirm password' fields do
not match. Please try again");
}
Litmus::Auth::doResetPassword($c->param('user'), $c->param('token'),
$c->param('password'));
print $c->header();
}
Litmus::Auth::requireLogin("index.cgi");
# if we end up here, it means the user was already logged in
# for some reason, so we should send a redirect to index.cgi:
print Litmus->cgi()->start_html(-title=>'Please Wait',
-head=>Litmus->cgi()->meta({-http_equiv=> 'refresh', -content=>'0;url=index.cgi'})
print $c->start_html(-title=>'Please Wait',
-head=>$c->meta({-http_equiv=> 'refresh', -content=>'0;url=index.cgi'})
);
print Litmus->cgi()->end_html();
print $c->end_html();
exit;

View File

@ -44,7 +44,7 @@ use JSON;
use Time::Piece::MySQL;
Litmus->init();
Litmus::Auth::requireAdmin("edit_categories.cgi");
Litmus::Auth::requireProductAdmin("edit_categories.cgi");
my $c = Litmus->cgi();
print $c->header();
@ -56,6 +56,16 @@ my $rebuild_cache = 0;
my $defaults;
if ($c->param) {
# auth:
# must be a super user for all changes but branch changes:
if ((!Litmus::Auth::getCurrentUser()->isSuperUser()) &&
($c->param("product_id") || $c->param("edit_product_form_mode") ||
$c->param("platform_id") || $c->param("edit_platform_form_mode") ||
$c->param("opsys_id") || $c->param("edit_opsys_form_mode") ||
$c->param("edit_opsys_form_opsys_id"))) {
Litmus::Auth::requireAdmin("manage_categories.cgi");
}
# Process product changes.
if ($c->param("delete_product_button") and
$c->param("product_id")) {
@ -250,6 +260,8 @@ if ($c->param) {
my $branch_id = $c->param("branch_id");
my $branch = Litmus::DB::Branch->retrieve($branch_id);
if ($branch) {
# must be a product admin
Litmus::Auth::requireProductAdmin("manage_categories.cgi", $branch->product());
$rv = $branch->delete;
if ($rv) {
$status = "success";
@ -264,6 +276,11 @@ if ($c->param) {
$message = "Branch ID# $branch_id does not exist. (Already deleted?)";
}
} elsif ($c->param("edit_branch_form_mode")) {
# need to be a product admin for the branch's product and for any
# product the branch may be moved into:
Litmus::Auth::requireProductAdmin("manage_categories",
$c->param('edit_branch_form_product_id'));
my $enabled = $c->param('edit_branch_form_enabled') ? 1 : 0;
if ($c->param("edit_branch_form_mode") eq "add") {
my %hash = (
@ -287,6 +304,10 @@ if ($c->param) {
my $branch_id = $c->param("edit_branch_form_branch_id");
my $branch = Litmus::DB::Branch->retrieve($branch_id);
if ($branch) {
# must be a product admin to edit the branch:
Litmus::Auth::requireProductAdmin('manage_categories.cgi',
$branch->product());
$branch->name($c->param('edit_branch_form_name'));
$branch->product_id($c->param('edit_branch_form_product_id'));
$branch->detect_regexp($c->param('edit_branch_form_detect_regexp') ? $c->param('edit_branch_form_detect_regexp') : '');
@ -321,6 +342,23 @@ my $testgroups = Litmus::FormWidget->getTestgroups();
my $opsyses = Litmus::FormWidget->getOpsyses();
my $locales = Litmus::FormWidget->getLocales;
# if the user is not a superuser, only allow them access to the
# branches they are product admins for
my $authorized_branches;
if (Litmus::Auth::getCurrentUser()->isSuperUser()) {
$authorized_branches = $branches;
} else {
my @tmp;
foreach my $b (@{$branches}) {
my %cur = %{$b};
if (Litmus::Auth::getCurrentUser()->isProductAdmin($cur{product_id})) {
push(@tmp, $b);
}
}
$authorized_branches = \@tmp;
}
$branches = $authorized_branches;
my $json = JSON->new(skipinvalid => 1, convblessed => 1);
my $products_js = $json->objToJson($products);
my $branches_js = $json->objToJson($branches);

View File

@ -76,7 +76,7 @@ if ($c->param("searchSubgroupList")) {
# anyone can use this script for its searching capabilities, but if we
# get here, then you need to be an admin:
Litmus::Auth::requireAdmin('manage_subgroups.cgi');
Litmus::Auth::requireProductAdmin('manage_subgroups.cgi');
if ($c->param("subgroup_id")) {
$subgroup_id = $c->param("subgroup_id");
@ -86,6 +86,7 @@ my $defaults;
if ($c->param("delete_subgroup_button")) {
my $subgroup = Litmus::DB::Subgroup->retrieve($subgroup_id);
if ($subgroup) {
Litmus::Auth::requireProductAdmin("manage_subgroups.cgi", $subgroup->product());
$rv = $subgroup->delete_with_refs();
if ($rv) {
$status = "success";
@ -100,6 +101,7 @@ if ($c->param("delete_subgroup_button")) {
}
} elsif ($c->param("clone_subgroup_button")) {
my $subgroup = Litmus::DB::Subgroup->retrieve($subgroup_id);
Litmus::Auth::requireProductAdmin("manage_subgroups.cgi", $subgroup->product());
my $new_subgroup = $subgroup->clone;
if ($new_subgroup) {
$status = "success";
@ -114,6 +116,7 @@ if ($c->param("delete_subgroup_button")) {
requireField('branch', $c->param('branch'));
my $enabled = $c->param('enabled') ? 1 : 0;
if ($c->param("mode") eq "add") {
Litmus::Auth::requireProductAdmin("manage_subgroups.cgi", $c->param('product'));
my %hash = (
name => $c->param('name'),
product_id => $c->param('product'),
@ -139,6 +142,7 @@ if ($c->param("delete_subgroup_button")) {
$subgroup_id = $c->param("editform_subgroup_id");
my $subgroup = Litmus::DB::Subgroup->retrieve($subgroup_id);
if ($subgroup) {
Litmus::Auth::requireProductAdmin("manage_subgroups.cgi", $subgroup->product());
$subgroup->product_id($c->param('product'));
$subgroup->branch_id($c->param('branch'));
$subgroup->enabled($enabled);
@ -177,6 +181,51 @@ my $testgroups = Litmus::FormWidget->getTestgroups(0);
my $subgroups = Litmus::FormWidget->getSubgroups(0,'name');
my $testcases = Litmus::FormWidget->getTestcases(0,'name');
# only allow the user access to the products they are product admins for
my %authorized_products;
my @tmp_products;
foreach my $b (@{$products}) {
my %cur = %{$b};
if (Litmus::Auth::getCurrentUser()->isProductAdmin($cur{product_id})) {
push(@tmp_products, $b);
$authorized_products{$cur{product_id}} = 1;
}
}
$products = \@tmp_products;
# likewise for branches:
my %authorized_branches;
my @tmp_branches;
foreach my $b (@{$branches}) {
my %cur = %{$b};
if ($authorized_products{$cur{product_id}}) {
push(@tmp_branches, $b);
$authorized_branches{$cur{branch_id}} = 1;
}
}
$branches = \@tmp_branches;
# and testgroups
my @tmp_testgroups;
foreach my $b (@{$testgroups}) {
my %cur = %{$b};
if ($authorized_products{$cur{product_id}}) {
push(@tmp_testgroups, $b);
}
}
$testgroups = \@tmp_testgroups;
# and of course, subgroups
my @tmp_subgroups;
foreach my $b (@{$subgroups}) {
my %cur = %{$b};
if ($authorized_products{$cur{product_id}}) {
push(@tmp_subgroups, $b);
}
}
$subgroups = \@tmp_subgroups;
my $json = JSON->new(skipinvalid => 1, convblessed => 1);
my $products_js = $json->objToJson($products);
my $branches_js = $json->objToJson($branches);

View File

@ -70,7 +70,7 @@ if ($c->param("searchTestRunList")) {
}
Litmus::Auth::requireAdmin('manage_test_runs.cgi');
Litmus::Auth::requireRunDayAdmin('manage_test_runs.cgi');
if ($c->param("test_run_id")) {
$test_run_id = $c->param("test_run_id");
@ -79,6 +79,7 @@ my $defaults;
if ($c->param("delete_test_run_button")) {
my $test_run = Litmus::DB::TestRun->retrieve($test_run_id);
if ($test_run) {
Litmus::Auth::requireProductAdmin("manage_test_runs.cgi", $test_run->product());
$rv = $test_run->delete_with_refs();
if ($rv) {
$status = "success";
@ -93,13 +94,20 @@ if ($c->param("delete_test_run_button")) {
}
} elsif ($c->param("clone_test_run_button")) {
my $test_run = Litmus::DB::TestRun->retrieve($test_run_id);
my $new_test_run = $test_run->clone;
if ($new_test_run) {
$status = "success";
$message = "Test run cloned successfully. New test_run ID# is " . $new_test_run->test_run_id;
$defaults->{'test_run_id'} = $new_test_run->test_run_id;
if ($test_run) {
Litmus::Auth::requireProductAdmin("manage_test_runs.cgi", $test_run->product());
my $new_test_run = $test_run->clone;
if ($new_test_run) {
Litmus::Auth::requireProductAdmin("manage_test_runs.cgi", $test_run->product());
$status = "success";
$message = "Test run cloned successfully. New test_run ID# is " . $new_test_run->test_run_id;
$defaults->{'test_run_id'} = $new_test_run->test_run_id;
} else {
$status = "failure";
$message = "Failed to clone Test run ID# $test_run_id.";
}
} else {
$status = "failure";
$status = "failure";
$message = "Failed to clone Test run ID# $test_run_id.";
}
} elsif ($c->param("mode")) {
@ -108,6 +116,9 @@ if ($c->param("delete_test_run_button")) {
requireField('branch', $c->param('branch'));
requireField('start_timestamp', $c->param('start_timestamp'));
requireField('finish_timestamp', $c->param('finish_timestamp'));
Litmus::Auth::requireProductAdmin("manage_test_runs.cgi", $c->param('product'));
my $enabled = $c->param('enabled') ? 1 : 0;
my $recommended = $c->param('recommended') ? 1 : 0;
my $now = &UnixDate("today", "%q");
@ -149,6 +160,7 @@ if ($c->param("delete_test_run_button")) {
$test_run_id = $c->param("editform_test_run_id");
my $test_run = Litmus::DB::TestRun->retrieve($test_run_id);
if ($test_run) {
Litmus::Auth::requireProductAdmin("manage_test_runs.cgi", $test_run->product());
$test_run->name($c->param('name'));
$test_run->description($c->param('description'));
$test_run->product_id($c->param('product'));
@ -203,6 +215,50 @@ my $platforms = Litmus::FormWidget->getPlatforms();
my $opsyses = Litmus::FormWidget->getOpsyses();
my $authors = Litmus::FormWidget->getAuthors();
# only allow the user access to the products they are product admins for
my %authorized_products;
my @tmp_products;
foreach my $b (@{$products}) {
my %cur = %{$b};
if (Litmus::Auth::getCurrentUser()->isProductAdmin($cur{product_id})) {
push(@tmp_products, $b);
$authorized_products{$cur{product_id}} = 1;
}
}
$products = \@tmp_products;
# likewise for branches:
my %authorized_branches;
my @tmp_branches;
foreach my $b (@{$branches}) {
my %cur = %{$b};
if ($authorized_products{$cur{product_id}}) {
push(@tmp_branches, $b);
$authorized_branches{$cur{branch_id}} = 1;
}
}
$branches = \@tmp_branches;
# testgroups
my @tmp_testgroups;
foreach my $b (@{$testgroups}) {
my %cur = %{$b};
if ($authorized_products{$cur{product_id}}) {
push(@tmp_testgroups, $b);
}
}
$testgroups = \@tmp_testgroups;
# and, of course, testruns
my @tmp_testruns;
foreach my $b (@{$test_runs}) {
my %cur = %{$b};
if ($authorized_products{$cur{product_id}}) {
push(@tmp_testruns, $b);
}
}
$test_runs = \@tmp_testruns;
my $json = JSON->new(skipinvalid => 1, convblessed => 1);
my $products_js = $json->objToJson($products);
my $branches_js = $json->objToJson($branches);

View File

@ -78,12 +78,17 @@ if ($c->param("searchTestcaseList")) {
# anyone can use this script for its searching capabilities, but if we
# get here, then you need to be an admin:
Litmus::Auth::requireAdmin('manage_testcases.cgi');
Litmus::Auth::requireProductAdmin('manage_testcases.cgi');
if ($c->param("testcase_id")) {
$testcase_id = $c->param("testcase_id");
if ($c->param("edit")) {
$edit = $testcase_id;
# show an error if they are not a product admin for that product
my $testcase = Litmus::DB::Testcase->retrieve($testcase_id);
if ($testcase) {
Litmus::Auth::requireProductAdmin("manage_testcases.cgi", $testcase->product());
}
}
}
@ -91,6 +96,7 @@ my $defaults;
if ($c->param("delete_testcase_button")) {
my $testcase = Litmus::DB::Testcase->retrieve($testcase_id);
if ($testcase) {
Litmus::Auth::requireProductAdmin("manage_testcases.cgi", $testcase->product());
$rv = $testcase->delete_with_refs();
if ($rv) {
$status = "success";
@ -105,6 +111,7 @@ if ($c->param("delete_testcase_button")) {
}
} elsif ($c->param("clone_testcase_button")) {
my $testcase = Litmus::DB::Testcase->retrieve($testcase_id);
Litmus::Auth::requireProductAdmin("manage_testcases.cgi", $testcase->product());
my $new_testcase = $testcase->clone;
if ($new_testcase) {
$status = "success";
@ -123,6 +130,7 @@ if ($c->param("delete_testcase_button")) {
my $community_enabled = $c->param('communityenabled') ? 1 : 0;
my $now = &UnixDate("today","%q");
if ($c->param("mode") eq "add") {
Litmus::Auth::requireProductAdmin("manage_testcases.cgi", $c->param('product'));
my %hash = (
summary => $c->param('summary'),
steps => $c->param('steps') ? $c->param('steps') : '',
@ -153,6 +161,7 @@ if ($c->param("delete_testcase_button")) {
$testcase_id = $c->param("editform_testcase_id");
my $testcase = Litmus::DB::Testcase->retrieve($testcase_id);
if ($testcase) {
Litmus::Auth::requireProductAdmin("manage_testcases.cgi", $testcase->product());
$testcase->summary($c->param('summary'));
$testcase->steps($c->param('steps') ? $c->param('steps') : '');
$testcase->expected_results($c->param('results') ? $c->param('results') : '');
@ -198,6 +207,60 @@ my $testcases = Litmus::FormWidget->getTestcases(0,'name');
my $authors = Litmus::FormWidget->getAuthors();
# only allow the user access to the products they are product admins for
my %authorized_products;
my @tmp_products;
foreach my $b (@{$products}) {
my %cur = %{$b};
if (Litmus::Auth::getCurrentUser()->isProductAdmin($cur{product_id})) {
push(@tmp_products, $b);
$authorized_products{$cur{product_id}} = 1;
}
}
$products = \@tmp_products;
# likewise for branches:
my %authorized_branches;
my @tmp_branches;
foreach my $b (@{$branches}) {
my %cur = %{$b};
if ($authorized_products{$cur{product_id}}) {
push(@tmp_branches, $b);
$authorized_branches{$cur{branch_id}} = 1;
}
}
$branches = \@tmp_branches;
# testgroups
my @tmp_testgroups;
foreach my $b (@{$testgroups}) {
my %cur = %{$b};
if ($authorized_products{$cur{product_id}}) {
push(@tmp_testgroups, $b);
}
}
$testgroups = \@tmp_testgroups;
# subgroups
my @tmp_subgroups;
foreach my $b (@{$subgroups}) {
my %cur = %{$b};
if ($authorized_products{$cur{product_id}}) {
push(@tmp_subgroups, $b);
}
}
$subgroups = \@tmp_subgroups;
# and, of course, testcases
my @tmp_testcases;
foreach my $b (@{$testcases}) {
my %cur = %{$b};
if ($authorized_products{$cur{product_id}}) {
push(@tmp_testcases, $b);
}
}
$testcases = \@tmp_testcases;
my $json = JSON->new(skipinvalid => 1, convblessed => 1);
my $products_js = $json->objToJson($products);
my $branches_js = $json->objToJson($branches);

View File

@ -44,7 +44,8 @@ use JSON;
use Time::Piece::MySQL;
Litmus->init();
Litmus::Auth::requireAdmin("manage_testdays.cgi");
Litmus::Auth::requireRunDayAdmin("manage_testdays.cgi");
Litmus::Auth::requireProductAdmin("manage_testdays.cgi");
my $c = Litmus->cgi();
print $c->header();
@ -54,6 +55,7 @@ my $status;
my $rv;
my $rebuild_cache = 0;
my $defaults;
my $warning;
if ($c->param) {
# Process testday changes.
@ -62,6 +64,7 @@ if ($c->param) {
my $testday_id = $c->param("testday_id");
my $testday = Litmus::DB::TestDay->retrieve($testday_id);
if ($testday) {
Litmus::Auth::requireProductAdmin("manage_testdays.cgi", $testday->product());
$rv = $testday->delete;
if ($rv) {
$status = "success";
@ -82,7 +85,10 @@ if ($c->param) {
start_timestamp => $c->param('edit_testday_form_start_timestamp'),
finish_timestamp => $c->param('edit_testday_form_finish_timestamp'),
);
if ($c->param('product')) {
Litmus::Auth::requireProductAdmin("manage_testdays.cgi", $c->param('product'));
$hash{product_id} = $c->param('product');
}
if ($c->param('branch')) {
@ -103,6 +109,15 @@ if ($c->param) {
if ($new_testday) {
$status = "success";
$message = "Testday added successfully. New testday ID# is " . $new_testday->testday_id;
# search for other testdays that overlap this one and let the user know about them:
my @runs = Litmus::DB::TestRun->search_daterange($hash{start_timestamp},
$hash{finish_timestamp});
if (@runs) {
$warning = 1;
}
$defaults->{'testday_id'} = $new_testday->testday_id;
$rebuild_cache=1;
} else {
@ -113,10 +128,12 @@ if ($c->param) {
my $testday_id = $c->param("edit_testday_form_testday_id");
my $testday = Litmus::DB::TestDay->retrieve($testday_id);
if ($testday) {
Litmus::Auth::requireProductAdmin("manage_testdays.cgi", $testday->product());
$testday->description($c->param('edit_testday_form_desc'));
$testday->start_timestamp($c->param('edit_testday_form_start_timestamp'));
$testday->finish_timestamp($c->param('edit_testday_form_finish_timestamp'));
if ($c->param('product')) {
Litmus::Auth::requireProductAdmin("manage_testdays.cgi", $c->param('product'));
$testday->product_id($c->param('product'));
}
if ($c->param('branch')) {
@ -172,7 +189,8 @@ my $vars = {
products => $products,
branches => $branches,
testdays => $testdays,
locales => $locales,
locales => $locales,
warning => $warning,
};
$vars->{'products_js'} = $products_js;

View File

@ -71,7 +71,7 @@ if ($c->param("searchTestgroupList")) {
# anyone can use this script for its searching capabilities, but if we
# get here, then you need to be an admin:
Litmus::Auth::requireAdmin('manage_testgroups.cgi');
Litmus::Auth::requireProductAdmin('manage_testgroups.cgi');
if ($c->param("testgroup_id")) {
$testgroup_id = $c->param("testgroup_id");
@ -81,6 +81,7 @@ my $defaults;
if ($c->param("delete_testgroup_button")) {
my $testgroup = Litmus::DB::Testgroup->retrieve($testgroup_id);
if ($testgroup) {
Litmus::Auth::requireProductAdmin("manage_testgroups.cgi", $testgroup->product());
$rv = $testgroup->delete_with_refs();
if ($rv) {
$status = "success";
@ -95,6 +96,7 @@ if ($c->param("delete_testgroup_button")) {
}
} elsif ($c->param("clone_testgroup_button")) {
my $testgroup = Litmus::DB::Testgroup->retrieve($testgroup_id);
Litmus::Auth::requireProductAdmin("manage_testgroups.cgi", $testgroup->product());
my $new_testgroup = $testgroup->clone;
if ($new_testgroup) {
$status = "success";
@ -109,6 +111,7 @@ if ($c->param("delete_testgroup_button")) {
requireField('branch', $c->param('branch'));
my $enabled = $c->param('enabled') ? 1 : 0;
if ($c->param("mode") eq "add") {
Litmus::Auth::requireProductAdmin("manage_testgroups.cgi", $c->param('product'));
my %hash = (
name => $c->param('name'),
product_id => $c->param('product'),
@ -133,6 +136,7 @@ if ($c->param("delete_testgroup_button")) {
$testgroup_id = $c->param("editform_testgroup_id");
my $testgroup = Litmus::DB::Testgroup->retrieve($testgroup_id);
if ($testgroup) {
Litmus::Auth::requireProductAdmin("manage_testgroups.cgi", $testgroup->product());
$testgroup->product_id($c->param('product'));
$testgroup->branch_id($c->param('branch'));
$testgroup->enabled($enabled);
@ -170,6 +174,41 @@ my $branches = Litmus::FormWidget->getBranches();
my $testgroups = Litmus::FormWidget->getTestgroups;
my $subgroups = Litmus::FormWidget->getSubgroups(0,'name');
# only allow the user access to the products they are product admins for
my %authorized_products;
my @tmp_products;
foreach my $b (@{$products}) {
my %cur = %{$b};
if (Litmus::Auth::getCurrentUser()->isProductAdmin($cur{product_id})) {
push(@tmp_products, $b);
$authorized_products{$cur{product_id}} = 1;
}
}
$products = \@tmp_products;
# likewise for branches:
my %authorized_branches;
my @tmp_branches;
foreach my $b (@{$branches}) {
my %cur = %{$b};
if ($authorized_products{$cur{product_id}}) {
push(@tmp_branches, $b);
$authorized_branches{$cur{branch_id}} = 1;
}
}
$branches = \@tmp_branches;
# and of course limit the testgroups
my @tmp_testgroups;
foreach my $b (@{$testgroups}) {
my %cur = %{$b};
if ($authorized_products{$cur{product_id}}) {
push(@tmp_testgroups, $b);
}
}
$testgroups = \@tmp_testgroups;
my $json = JSON->new(skipinvalid => 1, convblessed => 1);
my $products_js = $json->objToJson($products);
my $branches_js = $json->objToJson($branches);

View File

@ -57,6 +57,8 @@ our \$db_pass = "";
our \$user_cookiename = "litmus_login";
our \$sysconfig_cookiename = "litmustestingconfiguration";
our \$sendmail_path = "/usr/sbin/sendmail";
our \$tr_host = "";
our \$tr_name = "";
our \$tr_user = "";
@ -131,7 +133,7 @@ if ($reset_db) {
# and indicies to the schema. To do this, we use the helpful Litmus::DBTools
# module.
#
# NOTE: anything changed here should also be added to schema.pl for new
# NOTE: anything changed here must also be added to schema.pl for new
# installations
use Litmus::DBTools;
my $dbtool = Litmus::DBTools->new($dbh);
@ -256,23 +258,20 @@ $dbtool->AddKey("test_runs", 'version (version)', '');
$dbtool->AddField("test_run_testgroups", "sort_order", "smallint(6) not null default '1'");
$dbtool->AddKey("test_run_testgroups", 'sort_order (sort_order)', '');
# zll: upgrade to new-world group permission system
# do this in a separate package to avoid namespace polution if we're
# creating the intial db
$dbtool->RenameField("users", "is_admin", "is_admin_old");
package CreateAdminGroups;
require 'Litmus/DB/SecurityGroup.pm';
Litmus::DB::SecurityGroup->import();
Litmus::DB::SecurityGroup->upgradeGroups();
$dbtool->DropField("users", "is_admin_old");
package main;
print "Schema update complete.\n\n";
print <<EOS;
Due to the schema changes introduced, and depending on the when you last
updated your schema, you may now need to update/normalize your data as
well. This will include, but may not be limited to:
* populating the platform_products table;
* updating/normalizing platforms;
* updating/normalizing platform_ids in test_results;
* updating/normalizing opsyses;
* updating/normalizing opsys_ids in test_results;
* populating the subgroup_testgroups table;
* populating the testcase_subgroups table;
* filling in product_ids for each testcase/subgroup;
* any appropriate scripts in the migration/ subdir;
EOS
# javascript cache
print "Rebuilding JS cache...";

View File

@ -389,3 +389,30 @@ $table{users} =
index(is_admin),
index contact_info (email, realname, irc_nickname),
fulltext index contact_info_fulltext (email, realname, irc_nickname)';
$table{security_groups} =
'group_id mediumint not null primary key auto_increment,
name varchar(255) not null,
description varchar(255) not null,
grouptype tinyint not null,
isactive tinyint not null default 1,
unique index(name)';
$table{user_group_map} =
'user_id int(11) not null,
group_id mediumint not null,
unique(user_id, group_id)';
$table{group_product_map} =
'group_id mediumint not null,
product_id tinyint(4) not null,
unique(group_id, product_id)';
$table{password_resets} =
'user_id int(11) not null,
session_id int(11) not null,
index(user_id, session_id)';

View File

@ -41,9 +41,20 @@ var testgroups=[% testgroups_js %];
<div id="content">
[% INCLUDE admin/form_widgets/update_products.tmpl %]
[% INCLUDE admin/form_widgets/update_platforms.tmpl %]
[% INCLUDE admin/form_widgets/update_opsyses.tmpl %]
[% IF defaultemail.isSuperUser %]
[% INCLUDE admin/form_widgets/update_products.tmpl %]
[% INCLUDE admin/form_widgets/update_platforms.tmpl %]
[% INCLUDE admin/form_widgets/update_opsyses.tmpl %]
[% END %]
[% IF ! defaultemail.isSuperUser %]
<div class="login_important">
Note: as you are not a Litmus super-administrator, you will only be
permitted to edit branches. To manage products, platforms, or operating
systems, please contact an administrator.
</div>
[% END %]
[% INCLUDE admin/form_widgets/update_branches.tmpl %]
</div> <!--END content-->

View File

@ -20,6 +20,7 @@
[%# INTERFACE:
# $user - the user object to edit
# @groups - array of groups available on the system (required only for admins)
#%]
[% includeselects=1 %]
@ -78,11 +79,6 @@ function checkFormContents(f) {
<td><input name="enabled" type="checkbox" value="1"
[% IF user.enabled %] checked [% END %] /></td>
</tr>
<tr>
<td class="headerleft">Is An Admin?</td>
<td><input name="is_admin" type="checkbox" value="1"
[% IF user.is_admin %] checked [% END %] /></td>
</tr>
[% END %]
<tr>
@ -103,6 +99,26 @@ function checkFormContents(f) {
<td class="headerleft">Confirm New Password:</td>
<td><input name="edit_confirm_password" type="password" size="30" value="" /></td>
</tr>
[% IF show_admin %]
<tr>
<td colspan="3"><hr/></td>
</tr>
<tr>
<td class="headerleft">Group Memberships:</td>
<td>
<table class="manage">
[% FOREACH group=groups %]
<tr>
<td><input name="group_[%group.id | html %]" type="checkbox" value="1" [% IF user.inGroup(group) %] checked [%END%]></td>
<td>[% group.name FILTER html %]</td>
</tr>
[% END %]
</table>
</td>
</tr>
[% END %]
[% IF ! show_admin %]

View File

@ -19,7 +19,7 @@
#%]
[%# INTERFACE:
#
# @groups - all valid security groups
#%]
[% INCLUDE global/html_header.tmpl js_files=['js/SelectBoxes.js','js/FormValidation.js'] title='Edit Users' %]
@ -34,7 +34,7 @@
<div class="section-full">
[% INCLUDE admin/edit_users/searchform.html.tmpl %]
[% INCLUDE admin/edit_users/searchform.html.tmpl groups=groups %]
</div>

View File

@ -19,14 +19,25 @@
#%]
[%# INTERFACE:
#
# @groups - all valid security groups
#%]
<p>You may search by email address, real name, or irc nickname.</p>
<p>You may search by email address, real name, irc nickname, and group membership.</p>
<form action="edit_users.cgi" method="get" name="form" id="form">
List users matching
<input name="search_string" size="35" value="[% IF search_string %][% search_string | html %][% ELSE %][% user.email %][% END %]"/>
<input name="search_string" size="35" value="[% IF search_string %][% search_string | html %][% END %]"/>
<br /><br />
And belonging to group(s):
<table class="manage">
[% FOREACH group=groups %]
[% id = group.group_id %]
<tr>
<td><input name="group_[%group.id | html %]" type="checkbox" value="1" [% IF checked.$id %] checked [%END%]></td>
<td>[% group.name FILTER html %]</td>
</tr>
[% END %]
</table>
<input type="submit" name="submit" value="Search" />
</form>

View File

@ -10,7 +10,7 @@
<table border="0" cellspacing="0" cellpadding="5">
<tr>
<td>
[% INCLUDE form_widgets/select_testday_id.tmpl name="testday_id" placeholder=1 size=5 show_name=1 onchange="loadTestday();" %]
[% INCLUDE form_widgets/select_testday_id.tmpl name="testday_id" placeholder=1 size=10 show_name=1 onchange="loadTestday();" %]
</td>
</tr>
<tr>

View File

@ -119,7 +119,11 @@ if (em.selectedIndex >= 0) {
enableForm('edit_test_run_form');
} else {
disableForm('edit_test_run_form');
changeProduct();
}
var suffix='_filter';
changeProduct();filterList();
</script>
[% INCLUDE global/litmus_footer.tmpl %]

View File

@ -41,6 +41,18 @@ var testgroups=[% testgroups_js %];
<div id="content">
[% IF warning %]
<div class="error">
<h1 class="errorHeading">Warning: Testday Conflict Detected</h1>
<h4>
Your recently created testday has dates that overlap one or
more existing testdays. You should check for conflicts and coordinate with
the administrators of the other testdays to ensure that the events are
compatible.
</h4>
</div>
[% END %]
[% INCLUDE admin/form_widgets/update_testdays.tmpl %]
</div> <!--END content-->

View File

@ -72,6 +72,14 @@ function checkNewAccountFormContents(f) {
comparePasswords(f.password,f.password_confirm)
);
}
function forgotPasswordForm(f) {
document.getElementById('forgot_email').value =
document.getElementById('login_email').value;
return (
checkEmail(f.email)
);
}
</script>
<div id="page">
@ -100,14 +108,20 @@ function checkNewAccountFormContents(f) {
<table border=0>
<tr>
<td>Email:</td>
<td><input name="email" type="text" size="25"></td>
<td><input name="email" type="text" size="25" id="login_email"></td>
</tr>
<tr>
<td>Password:</td>
<td><input name="password" type="password" size="25"></td>
</tr>
</table>
<br /><input type="submit" name="Submit" value="Login">
<br />
<input type="submit" name="Submit" value="Login"> <br /><br />
</form>
<form name="litmus_forgot_password" action="[% return_to | none %]" method="post" onSubmit="return forgotPasswordForm(this);">
<input name="email" id="forgot_email" type="hidden">
<input name="login_type" type="hidden" value="forgot_password">
<input type="submit" name="forgot" value="Forgot Password">
</form>
</div>

View File

@ -0,0 +1,61 @@
[%# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1
#
# 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 Litmus.
#
# The Initial Developer of the Original Code is
# The Mozilla Corporation.
# Portions created by the Initial Developer are Copyright (C) 2007
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Zach Lipton <zach@zachlipton.com>
#
# ***** END LICENSE BLOCK *****
#%]
[%# INTERFACE:
# $user - the user to reset a password for
#%]
[% INCLUDE global/html_header.tmpl %]
[% INCLUDE global/litmus_header.tmpl %]
<div id="page">
[% INCLUDE sidebar/sidebar.tmpl %]
<div id="content">
<h1 class="firstHeading">[% title | html %]</h1>
<div class="section-full">
<div class="section-content">
<div class="login_info">
<h3>
An email message has been sent to your registered email address.
When you receive this message, please follow the enclosed link to
reset your password.
</h3>
If you need assistance with your login, please contact an operator in <a href="http://irc.mozilla.org/">irc.mozilla.org</a>, channel #qa.
</div>
</div>
</div>
</div> <!--END content-->
</div> <!--END page-->
[% INCLUDE global/html_footer.tmpl %]

View File

@ -0,0 +1,18 @@
To: [% user.email %]
From: Litmus <litmus-daemon@litmus.mozilla.org>
Subject: Litmus Password Reset
You, or someone claiming to be you, recently visited Litmus and asked
to reset the password for the account owned by [% user.email %].
To reset your password, please click the following link:
http://litmus.mozilla.org/login.cgi?resetPassword=[%token%]
(If you are having trouble, try copying and pasting the link into the Address
Bar of your web browser.)
Thank you for using Litmus,
The Mozilla QA Team

View File

@ -0,0 +1,91 @@
[%# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1
#
# 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 Litmus.
#
# The Initial Developer of the Original Code is
# The Mozilla Corporation.
# Portions created by the Initial Developer are Copyright (C) 2007
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
# Zach Lipton <zach@zachlipton.com>
#
# ***** END LICENSE BLOCK *****
#%]
[%# INTERFACE:
# $user - the user to reset a password for
#%]
[% INCLUDE global/html_header.tmpl js_files = ['js/FormValidation.js'] %]
[% INCLUDE global/litmus_header.tmpl %]
<script type="text/javascript">
function checkForm(f) {
return (comparePasswords(f.password,f.password_confirm));
}
</script>
<div id="page">
[% INCLUDE sidebar/sidebar.tmpl %]
<div id="content">
<h1 class="firstHeading">[% title | html %]</h1>
<div class="section-full">
<div class="section-content">
<div class="login_info">
<h3>
Changing password for user: [%user.email | html%].
</h3>
<h3>
Choose a new password below:
</h3>
</div>
<div class="login_form" style="height: 100%; padding:15px;">
<form name="reset_password" action="login.cgi" method="post" onSubmit="return checkForm(this);">
<input name="login_type" type="hidden" value="doResetPassword">
<input name="user" type="hidden" value="[% user.user_id | html %]">
<input name="token" type="hidden" value="[% token | html %]">
<table cellpadding="0" width="100%" border=0>
<tr>
<td>New Password:</td>
<td><input name="password" type="password" size="25"></td>
</tr>
<tr>
<td>Confirm Password:</td>
<td><input name="password_confirm" type="password" size="25"></td>
</tr>
<tr height="10px">
</tr>
<tr>
<td colspan="2"><input type="submit" name="Submit" value="Reset Password"></td>
</tr>
</table>
</form>
</div>
<br />
If you need assistance with your login, please contact an operator in <a href="http://irc.mozilla.org/">irc.mozilla.org</a>, channel #qa.
</div>
</div>
</div> <!--END content-->
</div> <!--END page-->
[% INCLUDE global/html_footer.tmpl %]

View File

@ -258,10 +258,12 @@ Admin-specific limiting criteria go here. These criteria are not available to re
<tr>
<td colspan="2" class="heading">Display only results:</td>
</tr>
<!--
<tr>
<td class="heading">&nbsp;&nbsp;From Trusted Sources:</td>
<td><input type="radio" id="trusted_only" name="trusted_only" value="1" />&nbsp;Trusted <input type="radio" id="trusted_only" name="trusted_only" value="0E0" />&nbsp;Untrusted <input type="radio" id="trusted_only" name="trusted_only" value="all" checked />&nbsp;All</td>
</tr>
-->
<tr>
<td class="heading">&nbsp;&nbsp;By <a class='help' name="showVettingHelpText" onclick="toggleHelp(vettingHelpTitle,vettingHelpText);">Vetting Status: <img class="inline" src="images/info.png" alt="What is vetting?" /></a></td>
<td><input type="radio" id="vetted_only" name="vetted_only" value="1" />&nbsp;Vetted <input type="radio" id="vetted_only" name="vetted_only" value="0E0" />&nbsp;Not Vetted <input type="radio" id="vetted_only" name="vetted_only" value="all" checked />&nbsp;All</td>

View File

@ -1,17 +1,23 @@
[% IF show_admin==1 %]
[% IF defaultemail && defaultemail.isInAdminGroup() %]
<div class="pagetools">
<div>
<h3>Admin</h3>
<ul>
[% IF defaultemail.isRunDayAdmin %]
<li><a href="manage_test_runs.cgi">Manage Test Runs</a></li>
[% END %]
<hr/>
<li><a href="manage_testcases.cgi">Manage Testcases</a></li>
<li><a href="manage_subgroups.cgi">Manage Subgroups</a></li>
<li><a href="manage_testgroups.cgi">Manage Testgroups</a></li>
<hr/>
<li><a href="manage_categories.cgi">Manage Categories</a></li>
<li><a href="manage_testdays.cgi">Manage Testdays</a></li>
<li><a href="edit_users.cgi">Manage Users</a></li>
[% IF defaultemail.isRunDayAdmin %]
<li><a href="manage_testdays.cgi">Manage Testdays</a></li>
[% END %]
[% IF defaultemail.isSuperUser %]
<li><a href="edit_users.cgi">Manage Users</a></li>
[% END %]
</ul>
</div>
</div>

View File

@ -70,7 +70,7 @@
<td><b>Regression Bug ID #:</b></td>
<td id="regression_bug_id_display[% IF ! show_edit %]_[% testcase.regression_bug_id | html %][% END %]">[% IF testcase.regression_bug_id %]<script>document.write('<a href="' + generateBugLink([% testcase.regression_bug_id %]) + '">[% testcase.regression_bug_id | html %]</a>');</script>[% ELSE %]None specified[% END %]</td>
</tr>
[% IF show_admin %]
[% IF show_edit %]
<tr>
<td><b>Enabled?</b></td>
<td><input id="enabled_display" name="enabled_display" type="checkbox" value="1" [% IF testcase.enabled %] checked[% END %] disabled></td>

View File

@ -71,7 +71,7 @@
<div id="finish_timestamp_text[% IF ! show_edit %]_[% test_run.test_run_id | html %][% END %]">[% test_run.finish_timestamp | html %]</div>
</td>
</tr>
[% IF show_admin %]
[% IF show_edit %]
<tr>
<td><b>Enabled?</b></td>
<td><input id="enabled_display" name="enabled_display" type="checkbox" value="1" [% IF test_run.enabled %] checked[% END %] disabled></td>