Install.pm 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. # -*- Mode: perl; indent-tabs-mode: nil -*-
  2. #
  3. # The contents of this file are subject to the Mozilla Public
  4. # License Version 1.1 (the "License"); you may not use this file
  5. # except in compliance with the License. You may obtain a copy of
  6. # the License at http://www.mozilla.org/MPL/
  7. #
  8. # Software distributed under the License is distributed on an "AS
  9. # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  10. # implied. See the License for the specific language governing
  11. # rights and limitations under the License.
  12. #
  13. # The Original Code is the Bugzilla Bug Tracking System.
  14. #
  15. # Contributor(s): Max Kanat-Alexander <mkanat@bugzilla.org>
  16. package Bugzilla::Install;
  17. # Functions in this this package can assume that the database
  18. # has been set up, params are available, localconfig is
  19. # available, and any module can be used.
  20. #
  21. # If you want to write an installation function that can't
  22. # make those assumptions, then it should go into one of the
  23. # packages under the Bugzilla::Install namespace.
  24. use strict;
  25. use Bugzilla::Constants;
  26. use Bugzilla::Error;
  27. use Bugzilla::Group;
  28. use Bugzilla::Product;
  29. use Bugzilla::User;
  30. use Bugzilla::User::Setting;
  31. use Bugzilla::Util qw(get_text);
  32. use Bugzilla::Version;
  33. sub SETTINGS {
  34. return {
  35. # 2005-03-03 travis@sedsystems.ca -- Bug 41972
  36. display_quips => { options => ["on", "off"], default => "on" },
  37. # 2005-03-10 travis@sedsystems.ca -- Bug 199048
  38. comment_sort_order => { options => ["oldest_to_newest", "newest_to_oldest",
  39. "newest_to_oldest_desc_first"],
  40. default => "oldest_to_newest" },
  41. # 2005-05-12 bugzilla@glob.com.au -- Bug 63536
  42. post_bug_submit_action => { options => ["next_bug", "same_bug", "nothing"],
  43. default => "next_bug" },
  44. # 2005-06-29 wurblzap@gmail.com -- Bug 257767
  45. csv_colsepchar => { options => [',',';'], default => ',' },
  46. # 2005-10-26 wurblzap@gmail.com -- Bug 291459
  47. zoom_textareas => { options => ["on", "off"], default => "on" },
  48. # 2005-10-21 LpSolit@gmail.com -- Bug 313020
  49. per_bug_queries => { options => ['on', 'off'], default => 'off' },
  50. # 2006-05-01 olav@bkor.dhs.org -- Bug 7710
  51. state_addselfcc => { options => ['always', 'never', 'cc_unless_role'],
  52. default => 'cc_unless_role' },
  53. # 2006-08-04 wurblzap@gmail.com -- Bug 322693
  54. skin => { subclass => 'Skin', default => 'Dusk' },
  55. # 2006-12-10 LpSolit@gmail.com -- Bug 297186
  56. lang => { subclass => 'Lang',
  57. default => ${Bugzilla->languages}[0] },
  58. # 2007-07-02 altlist@gmail.com -- Bug 225731
  59. quote_replies => { options => ['quoted_reply', 'simple_reply', 'off'],
  60. default => "quoted_reply" }
  61. }
  62. };
  63. use constant SYSTEM_GROUPS => (
  64. {
  65. name => 'admin',
  66. description => 'Administrators'
  67. },
  68. {
  69. name => 'tweakparams',
  70. description => 'Can change Parameters'
  71. },
  72. {
  73. name => 'editusers',
  74. description => 'Can edit or disable users'
  75. },
  76. {
  77. name => 'creategroups',
  78. description => 'Can create and destroy groups'
  79. },
  80. {
  81. name => 'editclassifications',
  82. description => 'Can create, destroy, and edit classifications'
  83. },
  84. {
  85. name => 'editcomponents',
  86. description => 'Can create, destroy, and edit components'
  87. },
  88. {
  89. name => 'editkeywords',
  90. description => 'Can create, destroy, and edit keywords'
  91. },
  92. {
  93. name => 'editbugs',
  94. description => 'Can edit all bug fields',
  95. userregexp => '.*'
  96. },
  97. {
  98. name => 'canconfirm',
  99. description => 'Can confirm a bug or mark it a duplicate'
  100. },
  101. {
  102. name => 'bz_canusewhines',
  103. description => 'User can configure whine reports for self'
  104. },
  105. {
  106. name => 'bz_sudoers',
  107. description => 'Can perform actions as other users'
  108. },
  109. # There are also other groups created in update_system_groups.
  110. );
  111. use constant DEFAULT_CLASSIFICATION => {
  112. name => 'Unclassified',
  113. description => 'Not assigned to any classification'
  114. };
  115. use constant DEFAULT_PRODUCT => {
  116. name => 'TestProduct',
  117. description => 'This is a test product.'
  118. . ' This ought to be blown away and replaced with real stuff in a'
  119. . ' finished installation of bugzilla.'
  120. };
  121. use constant DEFAULT_COMPONENT => {
  122. name => 'TestComponent',
  123. description => 'This is a test component in the test product database.'
  124. . ' This ought to be blown away and replaced with real stuff in'
  125. . ' a finished installation of Bugzilla.'
  126. };
  127. sub update_settings {
  128. my %settings = %{SETTINGS()};
  129. foreach my $setting (keys %settings) {
  130. add_setting($setting,
  131. $settings{$setting}->{options},
  132. $settings{$setting}->{default},
  133. $settings{$setting}->{subclass});
  134. }
  135. }
  136. sub update_system_groups {
  137. my $dbh = Bugzilla->dbh;
  138. # Create most of the system groups
  139. foreach my $definition (SYSTEM_GROUPS) {
  140. my $exists = new Bugzilla::Group({ name => $definition->{name} });
  141. $definition->{isbuggroup} = 0;
  142. Bugzilla::Group->create($definition) unless $exists;
  143. }
  144. # Certain groups need something done after they are created. We do
  145. # that here.
  146. # Make sure people who can whine at others can also whine.
  147. if (!new Bugzilla::Group({name => 'bz_canusewhineatothers'})) {
  148. my $whineatothers = Bugzilla::Group->create({
  149. name => 'bz_canusewhineatothers',
  150. description => 'Can configure whine reports for other users',
  151. isbuggroup => 0 });
  152. my $whine = new Bugzilla::Group({ name => 'bz_canusewhines' });
  153. $dbh->do('INSERT INTO group_group_map (grantor_id, member_id)
  154. VALUES (?,?)', undef, $whine->id, $whineatothers->id);
  155. }
  156. # Make sure sudoers are automatically protected from being sudoed.
  157. if (!new Bugzilla::Group({name => 'bz_sudo_protect'})) {
  158. my $sudo_protect = Bugzilla::Group->create({
  159. name => 'bz_sudo_protect',
  160. description => 'Can not be impersonated by other users',
  161. isbuggroup => 0 });
  162. my $sudo = new Bugzilla::Group({ name => 'bz_sudoers' });
  163. $dbh->do('INSERT INTO group_group_map (grantor_id, member_id)
  164. VALUES (?,?)', undef, $sudo_protect->id, $sudo->id);
  165. }
  166. # Re-evaluate all regexps, to keep them up-to-date.
  167. my $sth = $dbh->prepare(
  168. "SELECT profiles.userid, profiles.login_name, groups.id,
  169. groups.userregexp, user_group_map.group_id
  170. FROM (profiles CROSS JOIN groups)
  171. LEFT JOIN user_group_map
  172. ON user_group_map.user_id = profiles.userid
  173. AND user_group_map.group_id = groups.id
  174. AND user_group_map.grant_type = ?
  175. WHERE userregexp != '' OR user_group_map.group_id IS NOT NULL");
  176. my $sth_add = $dbh->prepare(
  177. "INSERT INTO user_group_map (user_id, group_id, isbless, grant_type)
  178. VALUES (?, ?, 0, " . GRANT_REGEXP . ")");
  179. my $sth_del = $dbh->prepare(
  180. "DELETE FROM user_group_map
  181. WHERE user_id = ? AND group_id = ? AND isbless = 0
  182. AND grant_type = " . GRANT_REGEXP);
  183. $sth->execute(GRANT_REGEXP);
  184. while (my ($uid, $login, $gid, $rexp, $present) = $sth->fetchrow_array()) {
  185. if ($login =~ m/$rexp/i) {
  186. $sth_add->execute($uid, $gid) unless $present;
  187. } else {
  188. $sth_del->execute($uid, $gid) if $present;
  189. }
  190. }
  191. }
  192. # This function should be called only after creating the admin user.
  193. sub create_default_product {
  194. my $dbh = Bugzilla->dbh;
  195. # Make the default Classification if it doesn't already exist.
  196. if (!$dbh->selectrow_array('SELECT 1 FROM classifications')) {
  197. my $class = DEFAULT_CLASSIFICATION;
  198. print get_text('install_default_classification',
  199. { name => $class->{name} }) . "\n";
  200. $dbh->do('INSERT INTO classifications (name, description)
  201. VALUES (?, ?)',
  202. undef, $class->{name}, $class->{description});
  203. }
  204. # And same for the default product/component.
  205. if (!$dbh->selectrow_array('SELECT 1 FROM products')) {
  206. my $default_prod = DEFAULT_PRODUCT;
  207. print get_text('install_default_product',
  208. { name => $default_prod->{name} }) . "\n";
  209. $dbh->do(q{INSERT INTO products (name, description)
  210. VALUES (?,?)},
  211. undef, $default_prod->{name}, $default_prod->{description});
  212. my $product = new Bugzilla::Product({name => $default_prod->{name}});
  213. # The default version.
  214. Bugzilla::Version::create(Bugzilla::Version::DEFAULT_VERSION, $product);
  215. # And we automatically insert the default milestone.
  216. $dbh->do(q{INSERT INTO milestones (product_id, value, sortkey)
  217. SELECT id, defaultmilestone, 0
  218. FROM products});
  219. # Get the user who will be the owner of the Product.
  220. # We pick the admin with the lowest id, or we insert
  221. # an invalid "0" into the database, just so that we can
  222. # create the component.
  223. my $admin_group = new Bugzilla::Group({name => 'admin'});
  224. my ($admin_id) = $dbh->selectrow_array(
  225. 'SELECT user_id FROM user_group_map WHERE group_id = ?
  226. ORDER BY user_id ' . $dbh->sql_limit(1),
  227. undef, $admin_group->id) || 0;
  228. my $default_comp = DEFAULT_COMPONENT;
  229. $dbh->do("INSERT INTO components (name, product_id, description,
  230. initialowner)
  231. VALUES (?, ?, ?, ?)", undef, $default_comp->{name},
  232. $product->id, $default_comp->{description}, $admin_id);
  233. }
  234. }
  235. sub create_admin {
  236. my ($params) = @_;
  237. my $dbh = Bugzilla->dbh;
  238. my $template = Bugzilla->template;
  239. my $admin_group = new Bugzilla::Group({ name => 'admin' });
  240. my $admin_inheritors =
  241. Bugzilla::User->flatten_group_membership($admin_group->id);
  242. my $admin_group_ids = join(',', @$admin_inheritors);
  243. my ($admin_count) = $dbh->selectrow_array(
  244. "SELECT COUNT(*) FROM user_group_map
  245. WHERE group_id IN ($admin_group_ids)");
  246. return if $admin_count;
  247. my %answer = %{Bugzilla->installation_answers};
  248. my $login = $answer{'ADMIN_EMAIL'};
  249. my $password = $answer{'ADMIN_PASSWORD'};
  250. my $full_name = $answer{'ADMIN_REALNAME'};
  251. if (!$login || !$password || !$full_name) {
  252. print "\n" . get_text('install_admin_setup') . "\n\n";
  253. }
  254. while (!$login) {
  255. print get_text('install_admin_get_email') . ' ';
  256. $login = <STDIN>;
  257. chomp $login;
  258. eval { Bugzilla::User->check_login_name_for_creation($login); };
  259. if ($@) {
  260. print $@ . "\n";
  261. undef $login;
  262. }
  263. }
  264. while (!defined $full_name) {
  265. print get_text('install_admin_get_name') . ' ';
  266. $full_name = <STDIN>;
  267. chomp($full_name);
  268. }
  269. if (!$password) {
  270. $password = _prompt_for_password(
  271. get_text('install_admin_get_password'));
  272. }
  273. my $admin = Bugzilla::User->create({ login_name => $login,
  274. realname => $full_name,
  275. cryptpassword => $password });
  276. make_admin($admin);
  277. }
  278. sub make_admin {
  279. my ($user) = @_;
  280. my $dbh = Bugzilla->dbh;
  281. $user = ref($user) ? $user
  282. : new Bugzilla::User(login_to_id($user, THROW_ERROR));
  283. my $admin_group = new Bugzilla::Group({ name => 'admin' });
  284. # Admins get explicit membership and bless capability for the admin group
  285. $dbh->selectrow_array("SELECT id FROM groups WHERE name = 'admin'");
  286. my $group_insert = $dbh->prepare(
  287. 'INSERT INTO user_group_map (user_id, group_id, isbless, grant_type)
  288. VALUES (?, ?, ?, ?)');
  289. # These are run in an eval so that we can ignore the error of somebody
  290. # already being granted these things.
  291. eval {
  292. $group_insert->execute($user->id, $admin_group->id, 0, GRANT_DIRECT);
  293. };
  294. eval {
  295. $group_insert->execute($user->id, $admin_group->id, 1, GRANT_DIRECT);
  296. };
  297. # Admins should also have editusers directly, even though they'll usually
  298. # inherit it. People could have changed their inheritance structure.
  299. my $editusers = new Bugzilla::Group({ name => 'editusers' });
  300. eval {
  301. $group_insert->execute($user->id, $editusers->id, 0, GRANT_DIRECT);
  302. };
  303. print "\n", get_text('install_admin_created', { user => $user }), "\n";
  304. }
  305. sub _prompt_for_password {
  306. my $prompt = shift;
  307. my $password;
  308. while (!$password) {
  309. # trap a few interrupts so we can fix the echo if we get aborted.
  310. local $SIG{HUP} = \&_password_prompt_exit;
  311. local $SIG{INT} = \&_password_prompt_exit;
  312. local $SIG{QUIT} = \&_password_prompt_exit;
  313. local $SIG{TERM} = \&_password_prompt_exit;
  314. system("stty","-echo") unless ON_WINDOWS; # disable input echoing
  315. print $prompt, ' ';
  316. $password = <STDIN>;
  317. chomp $password;
  318. print "\n", get_text('install_confirm_password'), ' ';
  319. my $pass2 = <STDIN>;
  320. chomp $pass2;
  321. eval { validate_password($password, $pass2); };
  322. if ($@) {
  323. print "\n$@\n";
  324. undef $password;
  325. }
  326. system("stty","echo") unless ON_WINDOWS;
  327. }
  328. return $password;
  329. }
  330. # This is just in case we get interrupted while getting a password.
  331. sub _password_prompt_exit {
  332. # re-enable input echoing
  333. system("stty","echo") unless ON_WINDOWS;
  334. exit 1;
  335. }
  336. sub reset_password {
  337. my $login = shift;
  338. my $user = Bugzilla::User->check($login);
  339. my $prompt = "\n" . get_text('install_reset_password', { user => $user });
  340. my $password = _prompt_for_password($prompt);
  341. $user->set_password($password);
  342. $user->update();
  343. print "\n", get_text('install_reset_password_done'), "\n";
  344. }
  345. 1;
  346. __END__
  347. =head1 NAME
  348. Bugzilla::Install - Functions and variables having to do with
  349. installation.
  350. =head1 SYNOPSIS
  351. use Bugzilla::Install;
  352. Bugzilla::Install::update_settings();
  353. =head1 DESCRIPTION
  354. This module is used primarily by L<checksetup.pl> during installation.
  355. This module contains functions that deal with general installation
  356. issues after the database is completely set up and configured.
  357. =head1 CONSTANTS
  358. =over
  359. =item C<SETTINGS>
  360. Contains information about Settings, used by L</update_settings()>.
  361. =back
  362. =head1 SUBROUTINES
  363. =over
  364. =item C<update_settings()>
  365. Description: Adds and updates Settings for users.
  366. Params: none
  367. Returns: nothing.
  368. =item C<create_default_product()>
  369. Description: Creates the default product and classification if
  370. they don't exist.
  371. Params: none
  372. Returns: nothing
  373. =back