diff --git a/include/class.csrf.php b/include/class.csrf.php index 94f103cb8277013ef4131035968cb0775487b6eb..a1c3aed21392d5932b6b1edb31108cc6d3bedf8a 100644 --- a/include/class.csrf.php +++ b/include/class.csrf.php @@ -53,12 +53,15 @@ Class CSRF { return $this->name; } - function getToken() { + function rotate() { + $this->csrf['token'] = sha1(session_id().Crypto::random(16).SECRET_SALT); + $this->csrf['time'] = time(); + } - if(!$this->csrf['token'] || $this->isExpired()) { + function getToken() { - $this->csrf['token'] = sha1(session_id().Crypto::random(16).SECRET_SALT); - $this->csrf['time'] = time(); + if (!$this->csrf['token'] || $this->isExpired()) { + $this->rotate(); } else { //Reset the timer $this->csrf['time'] = time(); diff --git a/include/class.dynamic_forms.php b/include/class.dynamic_forms.php index e620669c9339a1dcfe1f5f31e56b530f14c0909a..833eb318b97b581896b30744742d706a4d3bb089 100644 --- a/include/class.dynamic_forms.php +++ b/include/class.dynamic_forms.php @@ -1295,10 +1295,17 @@ class SelectionField extends FormField { return $this->getList()->getForm(); } function getSubFields() { + $fields = new ListObject(array( + new TextboxField(array( + // XXX: i18n: Change to a better word when the UI changes + 'label' => '['.__('Abbrev').']', + 'id' => 'abb', + )) + )); $form = $this->getList()->getForm(); - if ($form) - return $form->getFields(); - return array(); + if ($form && ($F = $form->getFields())) + $fields->extend($F); + return $fields; } function toString($items) { @@ -1405,9 +1412,21 @@ class SelectionField extends FormField { } function getFilterData() { + // Start with the filter data for the list item as the [0] index $data = array(parent::getFilterData()); - if (($v = $this->getClean()) instanceof DynamicListItem) { - $data = array_merge($data, $v->getFilterData()); + if (($v = $this->getClean())) { + // Add in the properties for all selected list items in sub + // labeled by their field id + foreach ($v as $id=>$L) { + if (!($li = DynamicListItem::lookup($id))) + continue; + foreach ($li->getFilterData() as $prop=>$value) { + if (!isset($data[$prop])) + $data[$prop] = $value; + else + $data[$prop] .= " $value"; + } + } } return $data; } @@ -1474,9 +1493,9 @@ class TypeaheadSelectionWidget extends ChoicesWidget { foreach ($this->field->getList()->getItems() as $i) $source[] = array( 'value' => $i->getValue(), 'id' => $i->getId(), - 'info' => sprintf('%s %s', + 'info' => sprintf('%s%s', $i->getValue(), - (($extra= $i->getAbbrev()) ? "-- $extra" : '')), + (($extra= $i->getAbbrev()) ? " — $extra" : '')), ); ?> <span style="display:inline-block"> @@ -1497,6 +1516,7 @@ class TypeaheadSelectionWidget extends ChoicesWidget { $('input#<?php echo $this->name; ?>_id') .attr('name', '<?php echo $this->name; ?>[' + item['id'] + ']') .val(item['value']); + return false; } }); }); @@ -1515,8 +1535,12 @@ class TypeaheadSelectionWidget extends ChoicesWidget { function getEnteredValue() { // Used to verify typeahead fields $data = $this->field->getSource(); - if (isset($data[$this->name.'_name'])) - return trim($data[$this->name.'_name']); + if (isset($data[$this->name.'_name'])) { + // Drop the extra part, if any + $v = $data[$this->name.'_name']; + $v = substr($v, 0, strrpos($v, ' — ')); + return trim($v); + } return parent::getValue(); } } diff --git a/include/class.forms.php b/include/class.forms.php index 5122ddd933bacf16c1bf3a91e46ca377067f48bd..3345669da755517199ec6a29d75ac76d8aa0147d 100644 --- a/include/class.forms.php +++ b/include/class.forms.php @@ -761,17 +761,6 @@ class FormField { return null; } - /** - * Indicates if the field provides for searching for something other - * than keywords. For instance, textbox fields can have hits by keyword - * searches alone, but selection fields should provide the option to - * match a specific value or set of values and therefore need to - * participate on any search builder. - */ - function hasSpecialSearch() { - return true; - } - function getConfigurationForm($source=null) { if (!$this->_cform) { $type = static::getFieldType($this->get('type')); @@ -880,10 +869,6 @@ class TextboxField extends FormField { ); } - function hasSpecialSearch() { - return false; - } - function validateEntry($value) { parent::validateEntry($value); $config = $this->getConfiguration(); @@ -955,10 +940,6 @@ class TextareaField extends FormField { ); } - function hasSpecialSearch() { - return false; - } - function display($value) { $config = $this->getConfiguration(); if ($config['html']) @@ -1011,10 +992,6 @@ class PhoneField extends FormField { ); } - function hasSpecialSearch() { - return false; - } - function validateEntry($value) { parent::validateEntry($value); $config = $this->getConfiguration(); @@ -1447,9 +1424,6 @@ class ThreadEntryField extends FormField { function isPresentationOnly() { return true; } - function hasSpecialSearch() { - return false; - } function getConfigurationOptions() { global $cfg; @@ -1904,10 +1878,6 @@ class FileUploadField extends FormField { ); } - function hasSpecialSearch() { - return false; - } - /** * Called from the ajax handler for async uploads via web clients. */ diff --git a/include/class.list.php b/include/class.list.php index df431b9efe231fbeaa36a8a7f93630c97e32cf62..be6baec233279867eb46081bf852ff2db180c8a0 100644 --- a/include/class.list.php +++ b/include/class.list.php @@ -596,6 +596,15 @@ class DynamicListItem extends VerySimpleModel implements CustomListItem { } } + function getFilterData() { + $data = array(); + foreach ($this->getConfigurationForm()->getFields() as $F) { + $data['.'.$F->get('id')] = $F->toString($F->value); + } + $data['.abb'] = (string) $this->get('extra'); + return $data; + } + function getTranslateTag($subtag) { return _H(sprintf('listitem.%s.%s', $subtag, $this->id)); } diff --git a/include/staff/header.inc.php b/include/staff/header.inc.php index b6afe4f38a37c1020426adc62c7da892b049a5c7..dd9945a1eaf27fe2d48024df9ea5c722b2c83f29 100644 --- a/include/staff/header.inc.php +++ b/include/staff/header.inc.php @@ -65,7 +65,7 @@ if (($lang = Internationalization::getCurrentLanguage()) echo sprintf('<div id="notice_bar">%s</div>', $ost->getNotice()); ?> <div id="header"> - <p id="info" class="pull-right"><?php echo sprintf(__('Welcome, %s.'), '<strong>'.$thisstaff->getFirstName().'</strong>'); ?> + <p id="info" class="pull-right no-pjax"><?php echo sprintf(__('Welcome, %s.'), '<strong>'.$thisstaff->getFirstName().'</strong>'); ?> <?php if($thisstaff->isAdmin() && !defined('ADMINPAGE')) { ?> | <a href="admin.php" class="no-pjax"><?php echo __('Admin Panel'); ?></a> diff --git a/include/upgrader/streams/core.sig b/include/upgrader/streams/core.sig index 0db3e30eb6ae95b9673f62de2041b1ff9cec8e6c..68f58c5ebd07db09e5e22e9d52f6f4a4c63a1c17 100644 --- a/include/upgrader/streams/core.sig +++ b/include/upgrader/streams/core.sig @@ -1 +1 @@ -d9e311ad50bfe981f4356500c1d584ce +9143a511719555e8f8f09b49523bd022 diff --git a/include/upgrader/streams/core/2d590ffa-d9e311ad.patch.sql b/include/upgrader/streams/core/2d590ffa-9143a511.patch.sql similarity index 94% rename from include/upgrader/streams/core/2d590ffa-d9e311ad.patch.sql rename to include/upgrader/streams/core/2d590ffa-9143a511.patch.sql index 4bf8eb68df8b75e0a17d87b39ae0760c8cf94461..f52e0bb25639e765aa47b617c3726e689dce1c2c 100644 --- a/include/upgrader/streams/core/2d590ffa-d9e311ad.patch.sql +++ b/include/upgrader/streams/core/2d590ffa-9143a511.patch.sql @@ -1,5 +1,5 @@ /* - * @signature d9e311ad50bfe981f4356500c1d584ce + * @signature 9143a511719555e8f8f09b49523bd022 * @version v1.9.6 * @title All collaborators have threads * @@ -47,5 +47,5 @@ DROP TABLE `%TABLE_PREFIX%_orig_msg_ids`; -- Finished with patch UPDATE `%TABLE_PREFIX%config` - SET `value` = 'd9e311ad50bfe981f4356500c1d584ce' + SET `value` = '9143a511719555e8f8f09b49523bd022' WHERE `key` = 'schema_signature' AND `namespace` = 'core'; diff --git a/login.php b/login.php index 201f840c5b5cf6f4b31bf7e2922eb22d4263db78..0d6f9e3d4c30a8df3f6e8aa83334b0582563022e 100644 --- a/login.php +++ b/login.php @@ -31,6 +31,20 @@ else $inc = 'login.inc.php'; $suggest_pwreset = false; + +// Check the CSRF token, and ensure that future requests will have to use a +// different CSRF token. This will help ward off both parallel and serial +// brute force attacks, because new tokens will have to be requested for +// each attempt. +if ($_POST) { + // Check CSRF token + if (!$ost->checkCSRFToken()) + Http::response(400, __('Valid CSRF Token Required')); + + // Rotate the CSRF token (original cannot be reused) + $ost->getCSRF()->rotate(); +} + if ($_POST && isset($_POST['luser'])) { if (!$_POST['luser']) $errors['err'] = __('Valid username or email address is required'); diff --git a/scp/login.php b/scp/login.php index 609a0c5eca29df70b768233f29c9eb38e4e5fe1c..6655239c2be175d57a10a49744bb45019b928032 100644 --- a/scp/login.php +++ b/scp/login.php @@ -31,6 +31,16 @@ $msg = $msg ?: ($content ? $content->getLocalName() : __('Authentication Require $dest=($dest && (!strstr($dest,'login.php') && !strstr($dest,'ajax.php')))?$dest:'index.php'; $show_reset = false; if($_POST) { + // Check the CSRF token, and ensure that future requests will have to + // use a different CSRF token. This will help ward off both parallel and + // serial brute force attacks, because new tokens will have to be + // requested for each attempt. + if (!$ost->checkCSRFToken()) + Http::response(400, __('Valid CSRF Token Required')); + + // Rotate the CSRF token (original cannot be reused) + $ost->getCSRF()->rotate(); + // Lookup support backends for this staff $username = trim($_POST['userid']); if ($user = StaffAuthenticationBackend::process($username, diff --git a/setup/inc/streams/core/install-mysql.sql b/setup/inc/streams/core/install-mysql.sql index ca83ca1230f5d082adb988908ff378a333fb5cba..26f129681118b2c67eea32dc5f4b74f78d30a656 100644 --- a/setup/inc/streams/core/install-mysql.sql +++ b/setup/inc/streams/core/install-mysql.sql @@ -709,7 +709,6 @@ CREATE TABLE `%TABLE_PREFIX%lock` ( `code` varchar(20), `created` datetime NOT NULL, PRIMARY KEY (`lock_id`), - UNIQUE KEY `ticket_id` (`ticket_id`), KEY `staff_id` (`staff_id`) ) DEFAULT CHARSET=utf8;