diff --git a/README.md b/README.md index 63eae4967a6558339879c92e98743dd950d7f56b..4f820dba8f8cef3a1e01289b326d334b03753788 100644 --- a/README.md +++ b/README.md @@ -86,3 +86,4 @@ osTicket is supported by several magical open source projects including: * [PEAR/Net_Socket](http://pear.php.net/package/Net_Socket) * [PEAR/Serivces_JSON](http://pear.php.net/package/Services_JSON) * [phplint](http://antirez.com/page/phplint.html) + * [Spyc](http://github.com/mustangostang/spyc) diff --git a/include/Spyc.php b/include/Spyc.php new file mode 100644 index 0000000000000000000000000000000000000000..17d67db3ea32baa729606efc7e42b6468b0d8131 --- /dev/null +++ b/include/Spyc.php @@ -0,0 +1,1084 @@ +<?php +/** + * Spyc -- A Simple PHP YAML Class + * @version 0.5.1 + * @author Vlad Andersen <vlad.andersen@gmail.com> + * @author Chris Wanstrath <chris@ozmm.org> + * @link http://code.google.com/p/spyc/ + * @copyright Copyright 2005-2006 Chris Wanstrath, 2006-2011 Vlad Andersen + * @license http://www.opensource.org/licenses/mit-license.php MIT License + * @package Spyc + */ + +if (!function_exists('spyc_load')) { + /** + * Parses YAML to array. + * @param string $string YAML string. + * @return array + */ + function spyc_load ($string) { + return Spyc::YAMLLoadString($string); + } +} + +if (!function_exists('spyc_load_file')) { + /** + * Parses YAML to array. + * @param string $file Path to YAML file. + * @return array + */ + function spyc_load_file ($file) { + return Spyc::YAMLLoad($file); + } +} + +if (!function_exists('spyc_dump')) { + /** + * Dumps array to YAML. + * @param array $data Array. + * @return string + */ + function spyc_dump ($data) { + return Spyc::YAMLDump($data, false, false, true); + } +} + +/** + * The Simple PHP YAML Class. + * + * This class can be used to read a YAML file and convert its contents + * into a PHP array. It currently supports a very limited subsection of + * the YAML spec. + * + * Usage: + * <code> + * $Spyc = new Spyc; + * $array = $Spyc->load($file); + * </code> + * or: + * <code> + * $array = Spyc::YAMLLoad($file); + * </code> + * or: + * <code> + * $array = spyc_load_file($file); + * </code> + * @package Spyc + */ +class Spyc { + + // SETTINGS + + const REMPTY = "\0\0\0\0\0"; + + /** + * Setting this to true will force YAMLDump to enclose any string value in + * quotes. False by default. + * + * @var bool + */ + var $setting_dump_force_quotes = false; + + /** + * Setting this to true will forse YAMLLoad to use syck_load function when + * possible. False by default. + * @var bool + */ + var $setting_use_syck_is_possible = false; + + + + /**#@+ + * @access private + * @var mixed + */ + var $_dumpIndent; + var $_dumpWordWrap; + var $_containsGroupAnchor = false; + var $_containsGroupAlias = false; + var $path; + var $result; + var $LiteralPlaceHolder = '___YAML_Literal_Block___'; + var $SavedGroups = array(); + var $indent; + /** + * Path modifier that should be applied after adding current element. + * @var array + */ + var $delayedPath = array(); + + /**#@+ + * @access public + * @var mixed + */ + var $_nodeId; + +/** + * Load a valid YAML string to Spyc. + * @param string $input + * @return array + */ + function load ($input) { + return $this->__loadString($input); + } + + /** + * Load a valid YAML file to Spyc. + * @param string $file + * @return array + */ + function loadFile ($file) { + return $this->__load($file); + } + + /** + * Load YAML into a PHP array statically + * + * The load method, when supplied with a YAML stream (string or file), + * will do its best to convert YAML in a file into a PHP array. Pretty + * simple. + * Usage: + * <code> + * $array = Spyc::YAMLLoad('lucky.yaml'); + * print_r($array); + * </code> + * @access public + * @return array + * @param string $input Path of YAML file or string containing YAML + */ + function YAMLLoad($input) { + $Spyc = new Spyc; + return $Spyc->__load($input); + } + + /** + * Load a string of YAML into a PHP array statically + * + * The load method, when supplied with a YAML string, will do its best + * to convert YAML in a string into a PHP array. Pretty simple. + * + * Note: use this function if you don't want files from the file system + * loaded and processed as YAML. This is of interest to people concerned + * about security whose input is from a string. + * + * Usage: + * <code> + * $array = Spyc::YAMLLoadString("---\n0: hello world\n"); + * print_r($array); + * </code> + * @access public + * @return array + * @param string $input String containing YAML + */ + function YAMLLoadString($input) { + $Spyc = new Spyc; + return $Spyc->__loadString($input); + } + + /** + * Dump YAML from PHP array statically + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. Pretty simple. Feel free to + * save the returned string as nothing.yaml and pass it around. + * + * Oh, and you can decide how big the indent is and what the wordwrap + * for folding is. Pretty cool -- just pass in 'false' for either if + * you want to use the default. + * + * Indent's default is 2 spaces, wordwrap's default is 40 characters. And + * you can turn off wordwrap by passing in 0. + * + * @access public + * @return string + * @param array $array PHP array + * @param int $indent Pass in false to use the default, which is 2 + * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40) + * @param int $no_opening_dashes Do not start YAML file with "---\n" + */ + function YAMLDump($array, $indent = false, $wordwrap = false, $no_opening_dashes = false) { + $spyc = new Spyc; + return $spyc->dump($array, $indent, $wordwrap, $no_opening_dashes); + } + + + /** + * Dump PHP array to YAML + * + * The dump method, when supplied with an array, will do its best + * to convert the array into friendly YAML. Pretty simple. Feel free to + * save the returned string as tasteful.yaml and pass it around. + * + * Oh, and you can decide how big the indent is and what the wordwrap + * for folding is. Pretty cool -- just pass in 'false' for either if + * you want to use the default. + * + * Indent's default is 2 spaces, wordwrap's default is 40 characters. And + * you can turn off wordwrap by passing in 0. + * + * @access public + * @return string + * @param array $array PHP array + * @param int $indent Pass in false to use the default, which is 2 + * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40) + */ + function dump($array,$indent = false,$wordwrap = false, $no_opening_dashes = false) { + // Dumps to some very clean YAML. We'll have to add some more features + // and options soon. And better support for folding. + + // New features and options. + if ($indent === false or !is_numeric($indent)) { + $this->_dumpIndent = 2; + } else { + $this->_dumpIndent = $indent; + } + + if ($wordwrap === false or !is_numeric($wordwrap)) { + $this->_dumpWordWrap = 40; + } else { + $this->_dumpWordWrap = $wordwrap; + } + + // New YAML document + $string = ""; + if (!$no_opening_dashes) $string = "---\n"; + + // Start at the base of the array and move through it. + if ($array) { + $array = (array)$array; + $previous_key = -1; + foreach ($array as $key => $value) { + if (!isset($first_key)) $first_key = $key; + $string .= $this->_yamlize($key,$value,0,$previous_key, $first_key, $array); + $previous_key = $key; + } + } + return $string; + } + + /** + * Attempts to convert a key / value array item to YAML + * @access private + * @return string + * @param $key The name of the key + * @param $value The value of the item + * @param $indent The indent of the current node + */ + function _yamlize($key,$value,$indent, $previous_key = -1, $first_key = 0, $source_array = null) { + if (is_array($value)) { + if (empty ($value)) + return $this->_dumpNode($key, array(), $indent, $previous_key, $first_key, $source_array); + // It has children. What to do? + // Make it the right kind of item + $string = $this->_dumpNode($key, $this->REMPTY, $indent, $previous_key, $first_key, $source_array); + // Add the indent + $indent += $this->_dumpIndent; + // Yamlize the array + $string .= $this->_yamlizeArray($value,$indent); + } elseif (!is_array($value)) { + // It doesn't have children. Yip. + $string = $this->_dumpNode($key, $value, $indent, $previous_key, $first_key, $source_array); + } + return $string; + } + + /** + * Attempts to convert an array to YAML + * @access private + * @return string + * @param $array The array you want to convert + * @param $indent The indent of the current level + */ + function _yamlizeArray($array,$indent) { + if (is_array($array)) { + $string = ''; + $previous_key = -1; + foreach ($array as $key => $value) { + if (!isset($first_key)) $first_key = $key; + $string .= $this->_yamlize($key, $value, $indent, $previous_key, $first_key, $array); + $previous_key = $key; + } + return $string; + } else { + return false; + } + } + + /** + * Returns YAML from a key and a value + * @access private + * @return string + * @param $key The name of the key + * @param $value The value of the item + * @param $indent The indent of the current node + */ + function _dumpNode($key, $value, $indent, $previous_key = -1, $first_key = 0, $source_array = null) { + // do some folding here, for blocks + if (is_string ($value) && ((strpos($value,"\n") !== false || strpos($value,": ") !== false || strpos($value,"- ") !== false || + strpos($value,"*") !== false || strpos($value,"#") !== false || strpos($value,"<") !== false || strpos($value,">") !== false || strpos ($value, ' ') !== false || + strpos($value,"[") !== false || strpos($value,"]") !== false || strpos($value,"{") !== false || strpos($value,"}") !== false) || strpos($value,"&") !== false || strpos($value, "'") !== false || strpos($value, "!") === 0 || + substr ($value, -1, 1) == ':') + ) { + $value = $this->_doLiteralBlock($value,$indent); + } else { + $value = $this->_doFolding($value,$indent); + } + + if ($value === array()) $value = '[ ]'; + if ($value === "") $value = '""'; + if (in_array ($value, array ('true', 'TRUE', 'false', 'FALSE', 'y', 'Y', 'n', 'N', 'null', 'NULL'), true)) { + $value = $this->_doLiteralBlock($value,$indent); + } + if (trim ($value) != $value) + $value = $this->_doLiteralBlock($value,$indent); + + if (is_bool($value)) { + $value = ($value) ? "true" : "false"; + } + + if ($value === null) $value = 'null'; + if ($value === "'" . $this->REMPTY . "'") $value = null; + + $spaces = str_repeat(' ',$indent); + + //if (is_int($key) && $key - 1 == $previous_key && $first_key===0) { + if (is_array ($source_array) && array_keys($source_array) === range(0, count($source_array) - 1)) { + // It's a sequence + $string = $spaces.'- '.$value."\n"; + } else { + // if ($first_key===0) trigger_error('Keys are all screwy. The first one was zero, now it\'s "'. $key .'"',E_USER_ERROR); + // It's mapped + if (strpos($key, ":") !== false || strpos($key, "#") !== false) { $key = '"' . $key . '"'; } + $string = rtrim ($spaces.$key.': '.$value)."\n"; + } + return $string; + } + + /** + * Creates a literal block for dumping + * @access private + * @return string + * @param $value + * @param $indent int The value of the indent + */ + function _doLiteralBlock($value,$indent) { + if ($value === "\n") return '\n'; + if (strpos($value, "\n") === false && strpos($value, "'") === false) { + return sprintf ("'%s'", $value); + } + if (strpos($value, "\n") === false && strpos($value, '"') === false) { + return sprintf ('"%s"', $value); + } + $exploded = explode("\n",$value); + $newValue = '|'; + $indent += $this->_dumpIndent; + $spaces = str_repeat(' ',$indent); + foreach ($exploded as $line) { + $newValue .= "\n" . $spaces . ($line); + } + return $newValue; + } + + /** + * Folds a string of text, if necessary + * @access private + * @return string + * @param $value The string you wish to fold + */ + function _doFolding($value,$indent) { + // Don't do anything if wordwrap is set to 0 + + if ($this->_dumpWordWrap !== 0 && is_string ($value) && strlen($value) > $this->_dumpWordWrap) { + $indent += $this->_dumpIndent; + $indent = str_repeat(' ',$indent); + $wrapped = wordwrap($value,$this->_dumpWordWrap,"\n$indent"); + $value = ">\n".$indent.$wrapped; + } else { + if ($this->setting_dump_force_quotes && is_string ($value) && $value !== $this->REMPTY) + $value = '"' . $value . '"'; + if (is_numeric($value) && is_string($value)) + $value = '"' . $value . '"'; + } + + + return $value; + } + +// LOADING FUNCTIONS + + function __load($input) { + $Source = $this->loadFromSource($input); + return $this->loadWithSource($Source); + } + + function __loadString($input) { + $Source = $this->loadFromString($input); + return $this->loadWithSource($Source); + } + + function loadWithSource($Source) { + if (empty ($Source)) return array(); + if ($this->setting_use_syck_is_possible && function_exists ('syck_load')) { + $array = syck_load (implode ("\n", $Source)); + return is_array($array) ? $array : array(); + } + + $this->path = array(); + $this->result = array(); + + $cnt = count($Source); + for ($i = 0; $i < $cnt; $i++) { + $line = $Source[$i]; + + $this->indent = strlen($line) - strlen(ltrim($line)); + $tempPath = $this->getParentPathByIndent($this->indent); + $line = $this->stripIndent($line, $this->indent); + if ($this->isComment($line)) continue; + if ($this->isEmpty($line)) continue; + $this->path = $tempPath; + + $literalBlockStyle = $this->startsLiteralBlock($line); + if ($literalBlockStyle) { + $line = rtrim ($line, $literalBlockStyle . " \n"); + $literalBlock = ''; + $line .= $this->LiteralPlaceHolder; + $literal_block_indent = strlen($Source[$i+1]) - strlen(ltrim($Source[$i+1])); + while (++$i < $cnt && $this->literalBlockContinues($Source[$i], $this->indent)) { + $literalBlock = $this->addLiteralLine($literalBlock, $Source[$i], $literalBlockStyle, $literal_block_indent); + } + $i--; + } + + // Strip out comments + if (strpos ($line, '#')) { + $line = preg_replace('/\s*#([^"\']+)$/','',$line); + } + + while (++$i < $cnt && $this->greedilyNeedNextLine($line)) { + $line = rtrim ($line, " \n\t\r") . ' ' . ltrim ($Source[$i], " \t"); + } + $i--; + + $lineArray = $this->_parseLine($line); + + if ($literalBlockStyle) + $lineArray = $this->revertLiteralPlaceHolder ($lineArray, $literalBlock); + + $this->addArray($lineArray, $this->indent); + + foreach ($this->delayedPath as $indent => $delayedPath) + $this->path[$indent] = $delayedPath; + + $this->delayedPath = array(); + + } + return $this->result; + } + + function loadFromSource ($input) { + if (!empty($input) && strpos($input, "\n") === false && file_exists($input)) + $input = file_get_contents($input); + + return $this->loadFromString($input); + } + + function loadFromString ($input) { + $lines = explode("\n",$input); + foreach ($lines as $k => $_) { + $lines[$k] = rtrim ($_, "\r"); + } + return $lines; + } + + /** + * Parses YAML code and returns an array for a node + * @access private + * @return array + * @param string $line A line from the YAML file + */ + function _parseLine($line) { + if (!$line) return array(); + $line = trim($line); + if (!$line) return array(); + + $array = array(); + + $group = $this->nodeContainsGroup($line); + if ($group) { + $this->addGroup($line, $group); + $line = $this->stripGroup ($line, $group); + } + + if ($this->startsMappedSequence($line)) + return $this->returnMappedSequence($line); + + if ($this->startsMappedValue($line)) + return $this->returnMappedValue($line); + + if ($this->isArrayElement($line)) + return $this->returnArrayElement($line); + + if ($this->isPlainArray($line)) + return $this->returnPlainArray($line); + + + return $this->returnKeyValuePair($line); + + } + + /** + * Finds the type of the passed value, returns the value as the new type. + * @access private + * @param string $value + * @return mixed + */ + function _toType($value) { + if ($value === '') return ""; + $first_character = $value[0]; + $last_character = substr($value, -1, 1); + + $is_quoted = false; + do { + if (!$value) break; + if ($first_character != '"' && $first_character != "'") break; + if ($last_character != '"' && $last_character != "'") break; + $is_quoted = true; + } while (0); + + if ($is_quoted) + return strtr(substr ($value, 1, -1), array ('\\"' => '"', '\'\'' => '\'', '\\\'' => '\'')); + + if (strpos($value, ' #') !== false && !$is_quoted) + $value = preg_replace('/\s+#(.+)$/','',$value); + + if (!$is_quoted) $value = str_replace('\n', "\n", $value); + + if ($first_character == '[' && $last_character == ']') { + // Take out strings sequences and mappings + $innerValue = trim(substr ($value, 1, -1)); + if ($innerValue === '') return array(); + $explode = $this->_inlineEscape($innerValue); + // Propagate value array + $value = array(); + foreach ($explode as $v) { + $value[] = $this->_toType($v); + } + return $value; + } + + if (strpos($value,': ')!==false && $first_character != '{') { + $array = explode(': ',$value); + $key = trim($array[0]); + array_shift($array); + $value = trim(implode(': ',$array)); + $value = $this->_toType($value); + return array($key => $value); + } + + if ($first_character == '{' && $last_character == '}') { + $innerValue = trim(substr ($value, 1, -1)); + if ($innerValue === '') return array(); + // Inline Mapping + // Take out strings sequences and mappings + $explode = $this->_inlineEscape($innerValue); + // Propagate value array + $array = array(); + foreach ($explode as $v) { + $SubArr = $this->_toType($v); + if (empty($SubArr)) continue; + if (is_array ($SubArr)) { + $array[key($SubArr)] = $SubArr[key($SubArr)]; continue; + } + $array[] = $SubArr; + } + return $array; + } + + if ($value == 'null' || $value == 'NULL' || $value == 'Null' || $value == '' || $value == '~') { + return null; + } + + if ( is_numeric($value) && preg_match ('/^(-|)[1-9]+[0-9]*$/', $value) ){ + $intvalue = (int)$value; + if ($intvalue != PHP_INT_MAX) + $value = $intvalue; + return $value; + } + + if (in_array($value, + array('true', 'on', '+', 'yes', 'y', 'True', 'TRUE', 'On', 'ON', 'YES', 'Yes', 'Y'))) { + return true; + } + + if (in_array(strtolower($value), + array('false', 'off', '-', 'no', 'n'))) { + return false; + } + + if (is_numeric($value)) { + if ($value === '0') return 0; + if (rtrim ($value, 0) === $value) + $value = (float)$value; + return $value; + } + + return $value; + } + + /** + * Used in inlines to check for more inlines or quoted strings + * @access private + * @return array + */ + function _inlineEscape($inline) { + // There's gotta be a cleaner way to do this... + // While pure sequences seem to be nesting just fine, + // pure mappings and mappings with sequences inside can't go very + // deep. This needs to be fixed. + + $seqs = array(); + $maps = array(); + $saved_strings = array(); + $saved_empties = array(); + + // Check for empty strings + $regex = '/("")|(\'\')/'; + if (preg_match_all($regex,$inline,$strings)) { + $saved_empties = $strings[0]; + $inline = preg_replace($regex,'YAMLEmpty',$inline); + } + unset($regex); + + // Check for strings + $regex = '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/'; + if (preg_match_all($regex,$inline,$strings)) { + $saved_strings = $strings[0]; + $inline = preg_replace($regex,'YAMLString',$inline); + } + unset($regex); + + // echo $inline; + + $i = 0; + do { + + // Check for sequences + while (preg_match('/\[([^{}\[\]]+)\]/U',$inline,$matchseqs)) { + $seqs[] = $matchseqs[0]; + $inline = preg_replace('/\[([^{}\[\]]+)\]/U', ('YAMLSeq' . (count($seqs) - 1) . 's'), $inline, 1); + } + + // Check for mappings + while (preg_match('/{([^\[\]{}]+)}/U',$inline,$matchmaps)) { + $maps[] = $matchmaps[0]; + $inline = preg_replace('/{([^\[\]{}]+)}/U', ('YAMLMap' . (count($maps) - 1) . 's'), $inline, 1); + } + + if ($i++ >= 10) break; + + } while (strpos ($inline, '[') !== false || strpos ($inline, '{') !== false); + + $explode = explode(',',$inline); + $explode = array_map('trim', $explode); + $stringi = 0; $i = 0; + + while (1) { + + // Re-add the sequences + if (!empty($seqs)) { + foreach ($explode as $key => $value) { + if (strpos($value,'YAMLSeq') !== false) { + foreach ($seqs as $seqk => $seq) { + $explode[$key] = str_replace(('YAMLSeq'.$seqk.'s'),$seq,$value); + $value = $explode[$key]; + } + } + } + } + + // Re-add the mappings + if (!empty($maps)) { + foreach ($explode as $key => $value) { + if (strpos($value,'YAMLMap') !== false) { + foreach ($maps as $mapk => $map) { + $explode[$key] = str_replace(('YAMLMap'.$mapk.'s'), $map, $value); + $value = $explode[$key]; + } + } + } + } + + + // Re-add the strings + if (!empty($saved_strings)) { + foreach ($explode as $key => $value) { + while (strpos($value,'YAMLString') !== false) { + $explode[$key] = preg_replace('/YAMLString/',$saved_strings[$stringi],$value, 1); + unset($saved_strings[$stringi]); + ++$stringi; + $value = $explode[$key]; + } + } + } + + + // Re-add the empties + if (!empty($saved_empties)) { + foreach ($explode as $key => $value) { + while (strpos($value,'YAMLEmpty') !== false) { + $explode[$key] = preg_replace('/YAMLEmpty/', '', $value, 1); + $value = $explode[$key]; + } + } + } + + $finished = true; + foreach ($explode as $key => $value) { + if (strpos($value,'YAMLSeq') !== false) { + $finished = false; break; + } + if (strpos($value,'YAMLMap') !== false) { + $finished = false; break; + } + if (strpos($value,'YAMLString') !== false) { + $finished = false; break; + } + if (strpos($value,'YAMLEmpty') !== false) { + $finished = false; break; + } + } + if ($finished) break; + + $i++; + if ($i > 10) + break; // Prevent infinite loops. + } + + + return $explode; + } + + function literalBlockContinues ($line, $lineIndent) { + if (!trim($line)) return true; + if (strlen($line) - strlen(ltrim($line)) > $lineIndent) return true; + return false; + } + + function referenceContentsByAlias ($alias) { + do { + if (!isset($this->SavedGroups[$alias])) { echo "Bad group name: $alias."; break; } + $groupPath = $this->SavedGroups[$alias]; + $value = $this->result; + foreach ($groupPath as $k) { + $value = $value[$k]; + } + } while (false); + return $value; + } + + function addArrayInline ($array, $indent) { + $CommonGroupPath = $this->path; + if (empty ($array)) return false; + + foreach ($array as $k => $_) { + $this->addArray(array($k => $_), $indent); + $this->path = $CommonGroupPath; + } + return true; + } + + function addArray ($incoming_data, $incoming_indent) { + + // print_r ($incoming_data); + + if (count ($incoming_data) > 1) + return $this->addArrayInline ($incoming_data, $incoming_indent); + + $key = key ($incoming_data); + $value = isset($incoming_data[$key]) ? $incoming_data[$key] : null; + if ($key === '__!YAMLZero') $key = '0'; + + if ($incoming_indent == 0 && !$this->_containsGroupAlias && !$this->_containsGroupAnchor) { // Shortcut for root-level values. + if ($key || $key === '' || $key === '0') { + $this->result[$key] = $value; + } else { + $this->result[] = $value; end ($this->result); $key = key ($this->result); + } + $this->path[$incoming_indent] = $key; + return; + } + + + + $history = array(); + // Unfolding inner array tree. + $history[] = $_arr = $this->result; + foreach ($this->path as $k) { + $history[] = $_arr = $_arr[$k]; + } + + if ($this->_containsGroupAlias) { + $value = $this->referenceContentsByAlias($this->_containsGroupAlias); + $this->_containsGroupAlias = false; + } + + + // Adding string or numeric key to the innermost level or $this->arr. + if (is_string($key) && $key == '<<') { + if (!is_array ($_arr)) { $_arr = array (); } + + $_arr = array_merge ($_arr, $value); + } else if ($key || $key === '' || $key === '0') { + if (!is_array ($_arr)) + $_arr = array ($key=>$value); + else + $_arr[$key] = $value; + } else { + if (!is_array ($_arr)) { $_arr = array ($value); $key = 0; } + else { $_arr[] = $value; end ($_arr); $key = key ($_arr); } + } + + $reverse_path = array_reverse($this->path); + $reverse_history = array_reverse ($history); + $reverse_history[0] = $_arr; + $cnt = count($reverse_history) - 1; + for ($i = 0; $i < $cnt; $i++) { + $reverse_history[$i+1][$reverse_path[$i]] = $reverse_history[$i]; + } + $this->result = $reverse_history[$cnt]; + + $this->path[$incoming_indent] = $key; + + if ($this->_containsGroupAnchor) { + $this->SavedGroups[$this->_containsGroupAnchor] = $this->path; + if (is_array ($value)) { + $k = key ($value); + if (!is_int ($k)) { + $this->SavedGroups[$this->_containsGroupAnchor][$incoming_indent + 2] = $k; + } + } + $this->_containsGroupAnchor = false; + } + + } + + function startsLiteralBlock ($line) { + $lastChar = substr (trim($line), -1); + if ($lastChar != '>' && $lastChar != '|') return false; + if ($lastChar == '|') return $lastChar; + // HTML tags should not be counted as literal blocks. + if (preg_match ('#<.*?>$#', $line)) return false; + return $lastChar; + } + + function greedilyNeedNextLine($line) { + $line = trim ($line); + if (!strlen($line)) return false; + if (substr ($line, -1, 1) == ']') return false; + if ($line[0] == '[') return true; + if (preg_match ('#^[^:]+?:\s*\[#', $line)) return true; + return false; + } + + function addLiteralLine ($literalBlock, $line, $literalBlockStyle, $indent = -1) { + $line = $this->stripIndent($line, $indent); + if ($literalBlockStyle !== '|') { + $line = $this->stripIndent($line); + } + $line = rtrim ($line, "\r\n\t ") . "\n"; + if ($literalBlockStyle == '|') { + return $literalBlock . $line; + } + if (strlen($line) == 0) + return rtrim($literalBlock, ' ') . "\n"; + if ($line == "\n" && $literalBlockStyle == '>') { + return rtrim ($literalBlock, " \t") . "\n"; + } + if ($line != "\n") + $line = trim ($line, "\r\n ") . " "; + return $literalBlock . $line; + } + + function revertLiteralPlaceHolder ($lineArray, $literalBlock) { + foreach ($lineArray as $k => $_) { + if (is_array($_)) + $lineArray[$k] = $this->revertLiteralPlaceHolder ($_, $literalBlock); + else if (substr($_, -1 * strlen ($this->LiteralPlaceHolder)) == $this->LiteralPlaceHolder) + $lineArray[$k] = rtrim ($literalBlock, " \r\n"); + } + return $lineArray; + } + + function stripIndent ($line, $indent = -1) { + if ($indent == -1) $indent = strlen($line) - strlen(ltrim($line)); + return substr ($line, $indent); + } + + function getParentPathByIndent ($indent) { + if ($indent == 0) return array(); + $linePath = $this->path; + do { + end($linePath); $lastIndentInParentPath = key($linePath); + if ($indent <= $lastIndentInParentPath) array_pop ($linePath); + } while ($indent <= $lastIndentInParentPath); + return $linePath; + } + + + function clearBiggerPathValues ($indent) { + + + if ($indent == 0) $this->path = array(); + if (empty ($this->path)) return true; + + foreach ($this->path as $k => $_) { + if ($k > $indent) unset ($this->path[$k]); + } + + return true; + } + + + function isComment ($line) { + if (!$line) return false; + if ($line[0] == '#') return true; + if (trim($line, " \r\n\t") == '---') return true; + return false; + } + + function isEmpty ($line) { + return (trim ($line) === ''); + } + + + function isArrayElement ($line) { + if (!$line) return false; + if ($line[0] != '-') return false; + if (strlen ($line) > 3) + if (substr($line,0,3) == '---') return false; + + return true; + } + + function isHashElement ($line) { + return strpos($line, ':'); + } + + function isLiteral ($line) { + if ($this->isArrayElement($line)) return false; + if ($this->isHashElement($line)) return false; + return true; + } + + + function unquote ($value) { + if (!$value) return $value; + if (!is_string($value)) return $value; + if ($value[0] == '\'') return trim ($value, '\''); + if ($value[0] == '"') return trim ($value, '"'); + return $value; + } + + function startsMappedSequence ($line) { + return ($line[0] == '-' && substr ($line, -1, 1) == ':'); + } + + function returnMappedSequence ($line) { + $array = array(); + $key = $this->unquote(trim(substr($line,1,-1))); + $array[$key] = array(); + $this->delayedPath = array(strpos ($line, $key) + $this->indent => $key); + return array($array); + } + + function returnMappedValue ($line) { + $array = array(); + $key = $this->unquote (trim(substr($line,0,-1))); + $array[$key] = ''; + return $array; + } + + function startsMappedValue ($line) { + return (substr ($line, -1, 1) == ':'); + } + + function isPlainArray ($line) { + return ($line[0] == '[' && substr ($line, -1, 1) == ']'); + } + + function returnPlainArray ($line) { + return $this->_toType($line); + } + + function returnKeyValuePair ($line) { + $array = array(); + $key = ''; + if (strpos ($line, ':')) { + // It's a key/value pair most likely + // If the key is in double quotes pull it out + if (($line[0] == '"' || $line[0] == "'") && preg_match('/^(["\'](.*)["\'](\s)*:)/',$line,$matches)) { + $value = trim(str_replace($matches[1],'',$line)); + $key = $matches[2]; + } else { + // Do some guesswork as to the key and the value + $explode = explode(':',$line); + $key = trim($explode[0]); + array_shift($explode); + $value = trim(implode(':',$explode)); + } + // Set the type of the value. Int, string, etc + $value = $this->_toType($value); + if ($key === '0') $key = '__!YAMLZero'; + $array[$key] = $value; + } else { + $array = array ($line); + } + return $array; + + } + + + function returnArrayElement ($line) { + if (strlen($line) <= 1) return array(array()); // Weird %) + $array = array(); + $value = trim(substr($line,1)); + $value = $this->_toType($value); + $array[] = $value; + return $array; + } + + + function nodeContainsGroup ($line) { + $symbolsForReference = 'A-z0-9_\-'; + if (strpos($line, '&') === false && strpos($line, '*') === false) return false; // Please die fast ;-) + if ($line[0] == '&' && preg_match('/^(&['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1]; + if ($line[0] == '*' && preg_match('/^(\*['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1]; + if (preg_match('/(&['.$symbolsForReference.']+)$/', $line, $matches)) return $matches[1]; + if (preg_match('/(\*['.$symbolsForReference.']+$)/', $line, $matches)) return $matches[1]; + if (preg_match ('#^\s*<<\s*:\s*(\*[^\s]+).*$#', $line, $matches)) return $matches[1]; + return false; + + } + + function addGroup ($line, $group) { + if ($group[0] == '&') $this->_containsGroupAnchor = substr ($group, 1); + if ($group[0] == '*') $this->_containsGroupAlias = substr ($group, 1); + //print_r ($this->path); + } + + function stripGroup ($line, $group) { + $line = trim(str_replace($group, '', $line)); + return $line; + } +} + +// Enable use of Spyc from command line +// The syntax is the following: php Spyc.php spyc.yaml + +do { + if (PHP_SAPI != 'cli') break; + if (empty ($_SERVER['argc']) || $_SERVER['argc'] < 2) break; + if (empty ($_SERVER['PHP_SELF']) || FALSE === strpos ($_SERVER['PHP_SELF'], 'Spyc.php') ) break; + $file = $argv[1]; + echo json_encode (spyc_load_file ($file)); +} while (0); diff --git a/include/ajax.content.php b/include/ajax.content.php index 7ba1a1d4c31adfb467e87d01ed856b8e547c909a..197d75ffe50c622296ca0bb22335874773fb499b 100644 --- a/include/ajax.content.php +++ b/include/ajax.content.php @@ -15,9 +15,9 @@ **********************************************************************/ if(!defined('INCLUDE_DIR')) die('!'); - + class ContentAjaxAPI extends AjaxController { - + function log($id) { if($id && ($log=Log::lookup($id))) { @@ -77,6 +77,8 @@ class ContentAjaxAPI extends AjaxController { <tr><td>%{assignee}</td><td>Assigned staff/team</td></tr> <tr><td>%{assigner}</td><td>Staff assigning the ticket</td></tr> <tr><td>%{url}</td><td>osTicket\'s base url (FQDN)</td></tr> + <tr><td>%{reset_link}</td> + <td>Reset link used by the password reset feature</td></tr> </table> </td> </tr> diff --git a/include/class.config.php b/include/class.config.php index a1dd1de245f0a5245396a2cf81369ced37f1e8eb..3726309876cf198d317e81e7988a7aa9f53476d5 100644 --- a/include/class.config.php +++ b/include/class.config.php @@ -20,11 +20,17 @@ class Config { var $config = array(); var $section = null; # Default namespace ('core') - var $table = 'config'; # Table name (with prefix) + var $table = CONFIG_TABLE; # Table name (with prefix) var $section_column = 'namespace'; # namespace column name var $session = null; # Session-backed configuration + # Defaults for this configuration. If settings don't exist in the + # database yet, the ->getInfo() method will not include the (default) + # values in the returned array. $defaults allows developers to define + # new settings and the corresponding default values. + var $defaults = array(); # List of default values + function Config($section=null) { if ($section) $this->section = $section; @@ -36,7 +42,7 @@ class Config { $_SESSION['cfg:'.$this->section] = array(); $this->session = &$_SESSION['cfg:'.$this->section]; - $sql='SELECT id, `key`, value FROM '.$this->table + $sql='SELECT id, `key`, value, `updated` FROM '.$this->table .' WHERE `'.$this->section_column.'` = '.db_input($this->section); if(($res=db_query($sql)) && db_num_rows($res)) @@ -49,7 +55,7 @@ class Config { } function getInfo() { - $info = array(); + $info = $this->defaults; foreach ($this->config as $key=>$setting) $info[$key] = $setting['value']; return $info; @@ -62,6 +68,8 @@ class Config { return $this->config[$key]['value']; elseif ($default !== null) return $this->set($key, $default); + elseif (isset($this->defaults[$key])) + return $this->defaults[$key]; return null; } @@ -78,6 +86,13 @@ class Config { return true; } + function lastModified($key) { + if (isset($this->config[$key])) + return $this->config[$key]['updated']; + else + return false; + } + function create($key, $value) { $sql = 'INSERT INTO '.$this->table .' SET `'.$this->section_column.'`='.db_input($this->section) @@ -124,6 +139,11 @@ class OsticketConfig extends Config { var $alertEmail; //Alert Email var $defaultSMTPEmail; //Default SMTP Email + var $defaults = array( + 'allow_pw_reset' => true, + 'pw_reset_mins' => 30, + ); + function OsticketConfig($section=null) { parent::Config($section); @@ -454,6 +474,30 @@ class OsticketConfig extends Config { return ($this->get('staff_ip_binding')); } + /** + * Configuration: allow_pw_reset + * + * TRUE if the <a>Forgot my password</a> link and system should be + * enabled, and FALSE otherwise. + */ + function allowPasswordReset() { + return $this->get('allow_pw_reset'); + } + + /** + * Configuration: pw_reset_window + * + * Number of minutes for which the password reset token is valid. + * + * Returns: Number of seconds the password reset token is valid. The + * number of minutes from the database is automatically converted + * to seconds here. + */ + function getPwResetWindow() { + // pw_reset_window is stored in minutes. Return value in seconds + return $this->get('pw_reset_window') * 60; + } + function isCaptchaEnabled() { return (extension_loaded('gd') && function_exists('gd_info') && $this->get('enable_captcha')); } @@ -724,6 +768,8 @@ class OsticketConfig extends Config { $f['datetime_format']=array('type'=>'string', 'required'=>1, 'error'=>'Datetime format required'); $f['daydatetime_format']=array('type'=>'string', 'required'=>1, 'error'=>'Day, Datetime format required'); $f['default_timezone_id']=array('type'=>'int', 'required'=>1, 'error'=>'Default Timezone required'); + $f['pw_reset_window']=array('type'=>'int', 'required'=>1, 'min'=>1, + 'error'=>'Valid password reset window required'); if(!Validator::process($f, $vars, $errors) || $errors) @@ -746,6 +792,8 @@ class OsticketConfig extends Config { 'client_max_logins'=>$vars['client_max_logins'], 'client_login_timeout'=>$vars['client_login_timeout'], 'client_session_timeout'=>$vars['client_session_timeout'], + 'allow_pw_reset'=>isset($vars['allow_pw_reset'])?1:0, + 'pw_reset_window'=>$vars['pw_reset_window'], 'time_format'=>$vars['time_format'], 'date_format'=>$vars['date_format'], 'datetime_format'=>$vars['datetime_format'], diff --git a/include/class.error.php b/include/class.error.php new file mode 100644 index 0000000000000000000000000000000000000000..ed5bf7e4c3d8d0384c63f0d8a4dbaa7af8b543a3 --- /dev/null +++ b/include/class.error.php @@ -0,0 +1,60 @@ +<?php +/********************************************************************* + class.error.php + + Error handling for PHP < 5.0. Allows for returning a formal error from a + function since throwing it isn't available. Also allows for consistent + logging and debugging of errors in the osTicket system log. + + Peter Rotich <peter@osticket.com> + Jared Hancock <jared@osticket.com> + Copyright (c) 2006-2013 osTicket + http://www.osticket.com + + Released under the GNU General Public License WITHOUT ANY WARRANTY. + See LICENSE.TXT for details. + + vim: expandtab sw=4 ts=4 sts=4: +**********************************************************************/ + +class Error /* extends Exception */ { + var $title = ''; + + function Error($message) { + call_user_func_array(array($this,'__construct'), func_get_args()); + } + function __construct($message) { + global $ost; + + $message = str_replace(ROOT_DIR, '(root)/', $message); + + if ($ost->getConfig()->getLogLevel() == 3) + $message .= "\n\n" . $this->formatBacktrace(debug_backtrace()); + + $ost->logError($this->getTitle(), $message); + } + + function getTitle() { + return get_class($this) . ": {$this->title}"; + } + + function formatBacktrace($bt) { + $buffer = array(); + foreach ($bt as $i=>$frame) + $buffer[] = sprintf("#%d %s%s%s at [%s:%d]", $i, + $frame['class'], $frame['type'], $frame['function'], + str_replace(ROOT_DIR, '', $frame['file']), $frame['line']); + return implode("\n", $buffer); + } +} + +class InitialDataError extends Error { + var $title = 'Problem with install initial data'; +} + +function raise_error($message, $class=false) { + if (!$class) $class = 'Error'; + new $class($message); +} + +?> diff --git a/include/class.misc.php b/include/class.misc.php index b6d9a673f6e311a085ce4ab07991e4c3b286f1f9..e913a8de0fdd052b0e9a87ec9693b720cc36e03b 100644 --- a/include/class.misc.php +++ b/include/class.misc.php @@ -14,26 +14,40 @@ vim: expandtab sw=4 ts=4 sts=4: **********************************************************************/ class Misc { - - function randCode($len=8) { - return substr(strtoupper(base_convert(microtime(),10,16)),0,$len); + + function randCode($count=8, $chars=false) { + $chars = $chars ? $chars + : 'abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + $data = ''; + $m = strlen($chars) - 1; + for ($i=0; $i < $count; $i++) + $data .= $chars[mt_rand(0,$m)]; + return $data; } - + + function __rand_seed($value=0) { + // Form a 32-bit figure for the random seed with the lower 16-bits + // the microseconds of the current time, and the upper 16-bits from + // received value + $seed = ((int) $value % 65535) << 16; + $seed += (int) ((double) microtime() * 1000000) % 65535; + mt_srand($seed); + } + /* Helper used to generate ticket IDs */ function randNumber($len=6,$start=false,$end=false) { - mt_srand ((double) microtime() * 1000000); $start=(!$len && $start)?$start:str_pad(1,$len,"0",STR_PAD_RIGHT); $end=(!$len && $end)?$end:str_pad(9,$len,"9",STR_PAD_RIGHT); - + return mt_rand($start,$end); } - /* misc date helpers...this will go away once we move to php 5 */ + /* misc date helpers...this will go away once we move to php 5 */ function db2gmtime($var){ global $cfg; if(!$var) return; - + $dbtime=is_int($var)?$var:strtotime($var); return $dbtime-($cfg->getDBTZoffset()*3600); } @@ -41,7 +55,7 @@ class Misc { //Take user time or gmtime and return db (mysql) time. function dbtime($var=null){ global $cfg; - + if(is_null($var) || !$var) $time=Misc::gmtime(); //gm time. else{ //user time to GM. @@ -52,7 +66,7 @@ class Misc { //gm to db time return $time+($cfg->getDBTZoffset()*3600); } - + /*Helper get GM time based on timezone offset*/ function gmtime() { return time()-date('Z'); @@ -67,7 +81,7 @@ class Misc { //Current page function currentURL() { - + $str = 'http'; if ($_SERVER['HTTPS'] == 'on') { $str .='s'; @@ -78,7 +92,7 @@ class Misc { if (isset($_SERVER['QUERY_STRING'])) { $_SERVER['REQUEST_URI'].='?'.$_SERVER['QUERY_STRING']; } - } + } if ($_SERVER['SERVER_PORT']!=80) { $str .= $_SERVER['SERVER_NAME'].':'.$_SERVER['SERVER_PORT'].$_SERVER['REQUEST_URI']; } else { @@ -92,7 +106,7 @@ class Misc { $hr =is_null($hr)?0:$hr; $min =is_null($min)?0:$min; - //normalize; + //normalize; if($hr>=24) $hr=$hr%24; elseif($hr<0) @@ -106,7 +120,7 @@ class Misc { $min=15; else $min=0; - + ob_start(); echo sprintf('<select name="%s" id="%s">',$name,$name); echo '<option value="" selected>Time</option>'; diff --git a/include/class.staff.php b/include/class.staff.php index a4edb4f7abd7e9a104d5ba182f87ff74dc9d62e4..19bd71bf02da54c1ee1fd6c36ac356c32f0ca7aa 100644 --- a/include/class.staff.php +++ b/include/class.staff.php @@ -15,6 +15,7 @@ **********************************************************************/ include_once(INCLUDE_DIR.'class.ticket.php'); include_once(INCLUDE_DIR.'class.dept.php'); +include_once(INCLUDE_DIR.'class.error.php'); include_once(INCLUDE_DIR.'class.team.php'); include_once(INCLUDE_DIR.'class.group.php'); include_once(INCLUDE_DIR.'class.passwd.php'); @@ -401,6 +402,7 @@ class Staff { //Staff profile update...unfortunately we have to separate it from admin update to avoid potential issues function updateProfile($vars, &$errors) { + global $cfg; $vars['firstname']=Format::striptags($vars['firstname']); $vars['lastname']=Format::striptags($vars['lastname']); @@ -437,7 +439,17 @@ class Staff { elseif($vars['passwd1'] && strcmp($vars['passwd1'], $vars['passwd2'])) $errors['passwd2']='Password(s) do not match'; - if(!$vars['cpasswd']) + if (($rtoken = $_SESSION['_staff']['reset-token'])) { + $_config = new Config('pwreset'); + if ($_config->get($rtoken) != $this->getId()) + $errors['err'] = + 'Invalid reset token. Logout and try again'; + elseif (!($ts = $_config->lastModified($rtoken)) + && ($cfg->getPwResetWindow() < (time() - strtotime($ts)))) + $errors['err'] = + 'Invalid reset token. Logout and try again'; + } + elseif(!$vars['cpasswd']) $errors['cpasswd']='Current password required'; elseif(!$this->cmp_passwd($vars['cpasswd'])) $errors['cpasswd']='Invalid current password!'; @@ -470,8 +482,10 @@ class Staff { .' ,default_paper_size='.db_input($vars['default_paper_size']); - if($vars['passwd1']) + if($vars['passwd1']) { $sql.=' ,change_passwd=0, passwdreset=NOW(), passwd='.db_input(Passwd::hash($vars['passwd1'])); + $this->cancelResetTokens(); + } $sql.=' WHERE staff_id='.db_input($this->getId()); @@ -576,7 +590,7 @@ class Staff { } function lookup($id) { - return ($id && is_numeric($id) && ($staff= new Staff($id)) && $staff->getId()==$id)?$staff:null; + return ($id && ($staff= new Staff($id)) && $staff->getId()) ? $staff : null; } function login($username, $passwd, &$errors, $strike=true) { @@ -600,31 +614,10 @@ class Staff { if($errors) return false; if(($user=new StaffSession(trim($username))) && $user->getId() && $user->check_passwd($passwd)) { - //update last login && password reset stuff. - $sql='UPDATE '.STAFF_TABLE.' SET lastlogin=NOW() '; - if($user->isPasswdResetDue() && !$user->isAdmin()) - $sql.=',change_passwd=1'; - $sql.=' WHERE staff_id='.db_input($user->getId()); - db_query($sql); - //Now set session crap and lets roll baby! - $_SESSION['_staff'] = array(); //clear. - $_SESSION['_staff']['userID'] = $username; - $user->refreshSession(); //set the hash. - $_SESSION['TZ_OFFSET'] = $user->getTZoffset(); - $_SESSION['TZ_DST'] = $user->observeDaylight(); - - //Log debug info. - $ost->logDebug('Staff login', - sprintf("%s logged in [%s]", $user->getUserName(), $_SERVER['REMOTE_ADDR'])); //Debug. - - //Regenerate session id. - $sid=session_id(); //Current id - session_regenerate_id(TRUE); - //Destroy old session ID - needed for PHP version < 5.1.0 TODO: remove when we move to php 5.3 as min. requirement. - if(($session=$ost->getSession()) && is_object($session) && $sid!=session_id()) - $session->destroy($sid); + self::_do_login($user, $username); Signal::send('auth.login.succeeded', $user); + $user->cancelResetTokens(); return $user; } @@ -651,6 +644,36 @@ class Staff { return false; } + function _do_login($user, $username) { + global $ost; + + //update last login && password reset stuff. + $sql='UPDATE '.STAFF_TABLE.' SET lastlogin=NOW() '; + if($user->isPasswdResetDue() && !$user->isAdmin()) + $sql.=',change_passwd=1'; + $sql.=' WHERE staff_id='.db_input($user->getId()); + db_query($sql); + //Now set session crap and lets roll baby! + $_SESSION['_staff'] = array(); //clear. + $_SESSION['_staff']['userID'] = $username; + $user->refreshSession(); //set the hash. + $_SESSION['TZ_OFFSET'] = $user->getTZoffset(); + $_SESSION['TZ_DST'] = $user->observeDaylight(); + + //Log debug info. + $ost->logDebug('Staff login', + sprintf("%s logged in [%s]", $user->getUserName(), $_SERVER['REMOTE_ADDR'])); //Debug. + + //Regenerate session id. + $sid=session_id(); //Current id + session_regenerate_id(TRUE); + //Destroy old session ID - needed for PHP version < 5.1.0 TODO: remove when we move to php 5.3 as min. requirement. + if(($session=$ost->getSession()) && is_object($session) && $sid!=session_id()) + $session->destroy($sid); + + return $user; + } + function create($vars, &$errors) { if(($id=self::save(0, $vars, $errors)) && $vars['teams'] && ($staff=Staff::lookup($id))) { $staff->updateTeams($vars['teams']); @@ -660,6 +683,43 @@ class Staff { return $id; } + function cancelResetTokens() { + // TODO: Drop password-reset tokens from the config table for + // this user id + $sql = 'DELETE FROM '.CONFIG_TABLE.' WHERE `namespace`="pwreset" + AND `value`='.db_input($this->getId()); + db_query($sql); + unset($_SESSION['_staff']['reset-token']); + } + + function sendResetEmail() { + global $ost, $cfg; + + if(!($tpl = $this->getDept()->getTemplate())) + $tpl= $ost->getConfig()->getDefaultTemplate(); + + $token = Misc::randCode(48); // 290-bits + if (!($template = $tpl->getMsgTemplate('staff.pwreset'))) + return new Error('Unable to retrieve password reset email template'); + + $msg = $ost->replaceTemplateVariables($template->asArray(), array( + 'url' => $ost->getConfig()->getBaseUrl(), + 'token' => $token, + 'reset_link' => sprintf( + "%s/scp/pwreset.php?token=%s", + $ost->getConfig()->getBaseUrl(), + $token), + )); + + if(!($email=$cfg->getAlertEmail())) + $email =$cfg->getDefaultEmail(); + + $_config = new Config('pwreset'); + $_config->set($token, $this->getId()); + + $email->send($this->getEmail(), $msg['subj'], $msg['body']); + } + function save($id, $vars, &$errors) { $vars['username']=Format::striptags($vars['username']); @@ -736,8 +796,9 @@ class Staff { .' ,signature='.db_input($vars['signature']) .' ,notes='.db_input($vars['notes']); - if($vars['passwd1']) + if($vars['passwd1']) { $sql.=' ,passwd='.db_input(Passwd::hash($vars['passwd1'])); + } if(isset($vars['change_passwd'])) $sql.=' ,change_passwd=1'; diff --git a/include/class.template.php b/include/class.template.php index c85297d2fd5f23f1cfb8b1b0df82bfc852369478..fd068d7a86047a8f8e6ad1a657827985f2494f44 100644 --- a/include/class.template.php +++ b/include/class.template.php @@ -13,6 +13,7 @@ vim: expandtab sw=4 ts=4 sts=4: **********************************************************************/ +require_once INCLUDE_DIR.'class.yaml.php'; class EmailTemplateGroup { @@ -56,6 +57,10 @@ class EmailTemplateGroup { 'ticket.overdue'=>array( 'name'=>'Overdue Ticket Alert', 'desc'=>'Alert sent to staff on stale or overdue tickets.'), + 'staff.pwreset' => array( + 'name' => 'Staff Password Reset', + 'desc' => 'Notice sent to staff with the password reset link.', + 'default' => 'templates/staff.pwreset.txt'), ); function EmailTemplateGroup($id){ @@ -108,6 +113,10 @@ class EmailTemplateGroup { return $this->isEnabled(); } + function getLanguage() { + return 'en_US'; + } + function isInUse(){ global $cfg; @@ -140,6 +149,9 @@ class EmailTemplateGroup { if ($tpl=EmailTemplate::lookupByName($this->getId(), $name, $this)) return $tpl; + if ($tpl=EmailTemplate::fromInitialData($name, $this)) + return $tpl; + $ost->logWarning('Template Fetch Error', "Unable to fetch '$name' template - id #".$this->getId()); return false; } @@ -330,11 +342,12 @@ class EmailTemplateGroup { class EmailTemplate { var $id; + var $ht; var $_group; function EmailTemplate($id, $group=null){ $this->id=0; - $this->load($id); + if ($id) $this->load($id); if ($group) $this->_group = $group; } @@ -466,5 +479,24 @@ class EmailTemplate { function lookup($id, $group=null) { return ($id && is_numeric($id) && ($t= new EmailTemplate($id, $group)) && $t->getId()==$id)?$t:null; } + + /** + * Load the template from the initial_data directory. The format of the + * file should be free flow text. The first line is the subject and the + * rest of the file is the body. + */ + function fromInitialData($name, $group=null) { + $templ = new EmailTemplate(0, $group); + $lang = ($group) ? $group->getLanguage() : 'en_US'; + $info = YamlDataParser::load(I18N_DIR . "$lang/templates/$name.yaml"); + if (isset($info['subject']) && isset($info['body'])) { + $templ->ht = $info; + return $templ; + } + raise_error("$lang/templates/$name.yaml: " + . 'Email templates must define both "subject" and "body" parts of the template', + 'InitialDataError'); + return false; + } } ?> diff --git a/include/class.validator.php b/include/class.validator.php index 0c76d0e199c079d3c9b0cb31599d5aa8784ada81..c0659d7535334d93adb71352491563eed9b1b686 100644 --- a/include/class.validator.php +++ b/include/class.validator.php @@ -3,7 +3,7 @@ class.validator.php Input validation helper. This class contains collection of functions used for data validation. - + Peter Rotich <peter@osticket.com> Copyright (c) 2006-2013 osTicket http://www.osticket.com @@ -28,11 +28,11 @@ class Validator { $this->fields=$fields; return (true); endif; - + return (false); } - - + + function validate($source,$userinput=true){ $this->errors=array(); @@ -56,7 +56,7 @@ class Validator { foreach($this->fields as $k=>$field){ if(!$field['required'] && !$this->input[$k]) //NOT required...and no data provided... continue; - + if($field['required'] && !isset($this->input[$k]) || (!$this->input[$k] && $field['type']!='int')){ //Required...and no data provided... $this->errors[$k]=$field['error']; continue; @@ -67,7 +67,9 @@ class Validator { case 'int': if(!is_numeric($this->input[$k])) $this->errors[$k]=$field['error']; - break; + elseif ($field['min'] && $this->input[$k] < $field['min']) + $this->errors[$k]=$field['error']; + break; case 'double': if(!is_numeric($this->input[$k])) $this->errors[$k]=$field['error']; @@ -114,7 +116,7 @@ class Validator { break; case 'zipcode': if(!is_numeric($this->input[$k]) || (strlen($this->input[$k])!=5)) - $this->errors[$k]=$field['error']; + $this->errors[$k]=$field['error']; break; default://If param type is not set...or handle..error out... $this->errors[$k]=$field['error'].' (type not set)'; @@ -122,15 +124,15 @@ class Validator { } return ($this->errors)?(FALSE):(TRUE); } - + function iserror(){ return $this->errors?true:false; } - + function errors(){ return $this->errors; } - + /*** Functions below can be called directly without class instance. Validator::func(var..); ***/ function is_email($email) { return preg_match('/^([*+!.&#$|\'\\%\/0-9a-z^_`{}=?~:-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})$/i',$email); @@ -140,17 +142,17 @@ class Validator { $stripped=preg_replace("(\(|\)|\-|\.|\+|[ ]+)","",$phone); return (!is_numeric($stripped) || ((strlen($stripped)<7) || (strlen($stripped)>16)))?false:true; } - + function is_url($url) { //XXX: parse_url is not ideal for validating urls but it's ideal for basic checks. return ($url && ($info=parse_url($url)) && $info['host']); } function is_ip($ip) { - + if(!$ip or empty($ip)) return false; - + $ip=trim($ip); # Thanks to http://stackoverflow.com/a/1934546 if (function_exists('inet_pton')) { # PHP 5.1.0 diff --git a/include/class.yaml.php b/include/class.yaml.php new file mode 100644 index 0000000000000000000000000000000000000000..d21605ca0b0cb04d178b36f38c7cf34cf0f5d253 --- /dev/null +++ b/include/class.yaml.php @@ -0,0 +1,41 @@ +<?php +/********************************************************************* + class.yaml.php + + Parses YAML data files into PHP associative arrays. Useful for initial + data shipped with osTicket. + + Currently, this module uses the pure-php implementation Spyc, written by + - Chris Wanstrath + - Vlad Andersen + and released under an MIT license. The software is available at + https://github.com/mustangostang/spyc + + Jared Hancock <jared@osticket.com> + Copyright (c) 2006-2013 osTicket + http://www.osticket.com + + Released under the GNU General Public License WITHOUT ANY WARRANTY. + See LICENSE.TXT for details. + + vim: expandtab sw=4 ts=4 sts=4: + $Id: $ +**********************************************************************/ + +require_once "Spyc.php"; + +class YamlDataParser { + /* static */ + function load($file) { + if (!file_exists($file)) { + raise_error("$file: File does not exist", 'YamlParserError'); + return false; + } + return Spyc::YAMLLoad($file); + } +} + +class YamlParserError extends Error { + var $title = 'Error parsing YAML document'; +} +?> diff --git a/include/i18n/en_US/templates/staff.pwreset.yaml b/include/i18n/en_US/templates/staff.pwreset.yaml new file mode 100644 index 0000000000000000000000000000000000000000..2ed5b3da46bd6dc3ce7595d40243caf249db9591 --- /dev/null +++ b/include/i18n/en_US/templates/staff.pwreset.yaml @@ -0,0 +1,20 @@ +# +# Email template: staff.pwreset +# +# Sent when a staff member requests a password reset via the <a>Forgot my +# password</a> link in the staff control login screen +# +--- +subject: osTicket Staff Password Reset +body: | + You or perhaps somebody you know has submitted a password reset request on + your behalf for the helpdesk at %{url}. + + If you feel that this has been done in error. Delete and disregard this + email. Your account is still secure and no one has been given access to it. + It is not locked and your password has not been reset. Someone could have + mistakenly entered your email address. + + Follow the link below to login to the help desk and change your password. + + %{reset_link} diff --git a/include/mysql.php b/include/mysql.php index 2a479072cf57eff4dcf6da1e2e7c026949c1a357..4e3bd7eb8caf242c92b3846fb1b7d5c845f31a87 100644 --- a/include/mysql.php +++ b/include/mysql.php @@ -23,6 +23,7 @@ return NULL; //Connect + $start = (double) microtime() * 1000000; if(!($dblink =@mysql_connect($host, $user, $passwd))) return NULL; @@ -36,6 +37,9 @@ @db_set_variable('sql_mode', ''); + // Use connection timing to seed the random number generator + Misc::__rand_seed(((double) microtime() * 1000000) - $start); + return $dblink; } diff --git a/include/mysqli.php b/include/mysqli.php index ced95434a971b729e5934b3f5754f8f25166aad8..ec369c652accad3f33af04eb085ee0e1ab88a584 100644 --- a/include/mysqli.php +++ b/include/mysqli.php @@ -39,6 +39,7 @@ function db_connect($host, $user, $passwd, $options = array()) { return NULL; //Connectr + $start = microtime(true); if(!@$__db->real_connect($host, $user, $passwd)) return NULL; @@ -52,6 +53,9 @@ function db_connect($host, $user, $passwd, $options = array()) { @db_set_variable('sql_mode', ''); + // Use connection timing to seed the random number generator + Misc::__rand_seed((microtime(true) - $start) * 1000000); + return $__db; } diff --git a/include/staff/login.header.php b/include/staff/login.header.php new file mode 100644 index 0000000000000000000000000000000000000000..679a509f96baa7f581bf8463048d3b642531d51a --- /dev/null +++ b/include/staff/login.header.php @@ -0,0 +1,22 @@ +<?php +defined('OSTSCPINC') or die('Invalid path'); +?> +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> + <meta http-equiv="content-type" content="text/html; charset=utf-8" /> + <title>osTicket:: SCP Login</title> + <link rel="stylesheet" href="css/login.css" type="text/css" /> + <meta name="robots" content="noindex" /> + <meta http-equiv="cache-control" content="no-cache" /> + <meta http-equiv="pragma" content="no-cache" /> + <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"> + <script type="text/javascript" src="../js/jquery-1.7.2.min.js"></script> + <script type="text/javascript"> + $(document).ready(function() { + $("input:not(.dp):visible:enabled:first").focus(); + }); + </script> +</head> +<body id="loginBody"> + diff --git a/include/staff/login.tpl.php b/include/staff/login.tpl.php index b8b136eb56d01282e8b15dbd4c0f11085e437eaa..6d5435732128d556219c0230bdfb66004bdc21e7 100644 --- a/include/staff/login.tpl.php +++ b/include/staff/login.tpl.php @@ -1,26 +1,7 @@ -<?php -defined('OSTSCPINC') or die('Invalid path'); - +<?php +include_once(INCLUDE_DIR.'staff/login.header.php'); $info = ($_POST && $errors)?Format::htmlchars($_POST):array(); ?> -<!DOCTYPE html> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> -<head> - <meta http-equiv="content-type" content="text/html; charset=utf-8" /> - <title>osTicket:: SCP Login</title> - <link rel="stylesheet" href="css/login.css" type="text/css" /> - <meta name="robots" content="noindex" /> - <meta http-equiv="cache-control" content="no-cache" /> - <meta http-equiv="pragma" content="no-cache" /> - <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"> - <script type="text/javascript" src="../js/jquery-1.7.2.min.js"></script> - <script type="text/javascript"> - $(document).ready(function() { - $("input:not(.dp):visible:enabled:first").focus(); - }); - </script> -</head> -<body id="loginBody"> <div id="loginBox"> <h1 id="logo"><a href="index.php">osTicket Staff Control Panel</a></h1> <h3><?php echo Format::htmlchars($msg); ?></h3> @@ -28,9 +9,12 @@ $info = ($_POST && $errors)?Format::htmlchars($_POST):array(); <?php csrf_token(); ?> <input type="hidden" name="do" value="scplogin"> <fieldset> - <input type="text" name="username" id="name" value="<?php echo $info['username']; ?>" placeholder="username" autocorrect="off" autocapitalize="off"> + <input type="text" name="userid" id="name" value="<?php echo $info['username']; ?>" placeholder="username" autocorrect="off" autocapitalize="off"> <input type="password" name="passwd" id="pass" placeholder="password" autocorrect="off" autocapitalize="off"> </fieldset> + <?php if ($_SESSION['_staff']['strikes'] > 1 && $cfg->allowPasswordReset()) { ?> + <h3 style="display:inline"><a href="pwreset.php">Forgot my password</a></h3> + <?php } ?> <input class="submit" type="submit" name="submit" value="Log In"> </form> </div> diff --git a/include/staff/profile.inc.php b/include/staff/profile.inc.php index 073a7c8a44229e1b18bf7b7b4cc738d8689d8396..2543c80d1cfa68444199a894f117c1521774afb4 100644 --- a/include/staff/profile.inc.php +++ b/include/staff/profile.inc.php @@ -190,6 +190,7 @@ $info['id']=$staff->getId(); <em><strong>Password</strong>: To reset your password, provide your current password and a new password below. <span class="error"> <?php echo $errors['passwd']; ?></span></em> </th> </tr> + <?php if (!isset($_SESSION['_staff']['reset-token'])) { ?> <tr> <td width="180"> Current Password: @@ -199,6 +200,7 @@ $info['id']=$staff->getId(); <span class="error"> <?php echo $errors['cpasswd']; ?></span> </td> </tr> + <?php } ?> <tr> <td width="180"> New Password: diff --git a/include/staff/pwreset.login.php b/include/staff/pwreset.login.php new file mode 100644 index 0000000000000000000000000000000000000000..6f93f1f0118093aefd5eb7b67568a560d2fb9066 --- /dev/null +++ b/include/staff/pwreset.login.php @@ -0,0 +1,26 @@ +<?php +include_once(INCLUDE_DIR.'staff/login.header.php'); +defined('OSTSCPINC') or die('Invalid path'); +$info = ($_POST)?Format::htmlchars($_POST):array(); +?> + +<div id="loginBox"> + <h1 id="logo"><a href="index.php">osTicket Staff Password Reset</a></h1> + <h3><?php echo Format::htmlchars($msg); ?></h3> + + <form action="pwreset.php" method="post"> + <?php csrf_token(); ?> + <input type="hidden" name="do" value="newpasswd"/> + <input type="hidden" name="token" value="<?php echo $_REQUEST['token']; ?>"/> + <fieldset> + <input type="text" name="userid" id="name" value="<?php echo + $info['userid']; ?>" placeholder="username or email" + autocorrect="off" autocapitalize="off"/> + </fieldset> + <input class="submit" type="submit" name="submit" value="Login"/> + </form> +</div> + +<div id="copyRights">Copyright © <a href='http://www.osticket.com' target="_blank">osTicket.com</a></div> +</body> +</html> diff --git a/include/staff/pwreset.php b/include/staff/pwreset.php new file mode 100644 index 0000000000000000000000000000000000000000..6aadeb2fcf10dbb308ed7ec3f548f4afd6c94ee8 --- /dev/null +++ b/include/staff/pwreset.php @@ -0,0 +1,25 @@ +<?php +include_once(INCLUDE_DIR.'staff/login.header.php'); +defined('OSTSCPINC') or die('Invalid path'); +$info = ($_POST && $errors)?Format::htmlchars($_POST):array(); +?> + +<div id="loginBox"> + <h1 id="logo"><a href="index.php">osTicket Staff Password Reset</a></h1> + <h3><?php echo Format::htmlchars($msg); ?></h3> + <form action="pwreset.php" method="post"> + <?php csrf_token(); ?> + <input type="hidden" name="do" value="sendmail"> + <fieldset> + <input type="text" name="userid" id="name" value="<?php echo + $info['userid']; ?>" placeholder="username" autocorrect="off" + autocapitalize="off"> + </fieldset> + <input class="submit" type="submit" name="submit" value="Send Email"/> + </form> + +</div> + +<div id="copyRights">Copyright © <a href='http://www.osticket.com' target="_blank">osTicket.com</a></div> +</body> +</html> diff --git a/include/staff/pwreset.sent.php b/include/staff/pwreset.sent.php new file mode 100644 index 0000000000000000000000000000000000000000..832b78ef57470c4045ed615c152dc228eb414e79 --- /dev/null +++ b/include/staff/pwreset.sent.php @@ -0,0 +1,22 @@ +<?php +include_once(INCLUDE_DIR.'staff/login.header.php'); +defined('OSTSCPINC') or die('Invalid path'); +$info = ($_POST && $errors)?Format::htmlchars($_POST):array(); +?> + +<div id="loginBox"> + <h1 id="logo"><a href="index.php">osTicket Staff Password Reset</a></h1> + <h3>A confirmation email has been sent</h3> + <h3 style="color:black;"><em> + A password reset email was sent to the email on file for your account. + Follow the link in the email to reset your password. + </em></h3> + + <form action="index.php" method="get"> + <input class="submit" type="submit" name="submit" value="Login"/> + </form> +</div> + +<div id="copyRights">Copyright © <a href='http://www.osticket.com' target="_blank">osTicket.com</a></div> +</body> +</html> diff --git a/include/staff/settings-system.inc.php b/include/staff/settings-system.inc.php index 6fade6d964b1fab6cca118fa0b8cff86841aa417..8915c8b4f87048d60cfba3a4d0fe3e2ca697dc62 100644 --- a/include/staff/settings-system.inc.php +++ b/include/staff/settings-system.inc.php @@ -112,7 +112,12 @@ $gmtime = Misc::gmtime(); </select> </td> </tr> - <tr><td>Password Reset Policy:</th> + <tr> + <th colspan="2"> + <em><b>Authentication Settings</b></em> + </th> + </tr> + <tr><td>Password Change Policy:</th> <td> <select name="passwd_reset_period"> <option value="0"> — None —</option> @@ -126,10 +131,20 @@ $gmtime = Misc::gmtime(); <font class="error"> <?php echo $errors['passwd_reset_period']; ?></font> </td> </tr> - <tr><td>Bind Staff Session to IP:</td> + <tr><td>Allow Password Resets:</th> <td> - <input type="checkbox" name="staff_ip_binding" <?php echo $config['staff_ip_binding']?'checked="checked"':''; ?>> - <em>(binds staff session to originating IP address upon login)</em> + <input type="checkbox" name="allow_pw_reset" <?php echo $config['allow_pw_reset']?'checked="checked"':''; ?>> + <em>Enables the <u>Forgot my password</u> link on the staff + control panel</em> + </td> + </tr> + <tr><td>Password Reset Window:</th> + <td> + <input type="text" name="pw_reset_window" size="6" value="<?php + echo $config['pw_reset_window']; ?>"> + Maximum time <em>in minutes</em> a password reset token can + be valid. + <font class="error"> <?php echo $errors['pw_reset_window']; ?></font> </td> </tr> <tr><td>Staff Excessive Logins:</td> @@ -182,6 +197,12 @@ $gmtime = Misc::gmtime(); Maximum idle time in minutes before a client must log in again (enter 0 to disable). </td> </tr> + <tr><td>Bind Staff Session to IP:</td> + <td> + <input type="checkbox" name="staff_ip_binding" <?php echo $config['staff_ip_binding']?'checked="checked"':''; ?>> + <em>(binds staff session to originating IP address upon login)</em> + </td> + </tr> <tr> <th colspan="2"> <em><b>Date and Time Options</b>: Please refer to <a href="http://php.net/date" target="_blank">PHP Manual</a> for supported parameters.</em> diff --git a/include/staff/tpl.inc.php b/include/staff/tpl.inc.php index 8c1ede75e5e4ed12092c08201d22a0aebeaa2cc2..c5954090dbd4935f5161851620dbed063c98b13c 100644 --- a/include/staff/tpl.inc.php +++ b/include/staff/tpl.inc.php @@ -6,15 +6,23 @@ if (is_a($template, EmailTemplateGroup)) { $id = 0; $tpl_id = $template->getId(); $name = $template->getName(); + $group = $template; $selected = $_REQUEST['code_name']; $action = 'implement'; $extras = array('code_name'=>$selected, 'tpl_id'=>$tpl_id); $msgtemplates=$template->all_names; + // Attempt to lookup the default data if it is defined + $default = @$template->getMsgTemplate($selected); + if ($default) { + $info['subj'] = $default->getSubject(); + $info['body'] = $default->getBody(); + } } else { // Template edit $id = $template->getId(); $tpl_id = $template->getTplId(); $name = $template->getGroup()->getName(); + $group = $template->getGroup(); $selected = $template->getCodeName(); $action = 'updatetpl'; $extras = array(); @@ -33,7 +41,7 @@ $tpl=$msgtemplates[$info['tpl']]; <select id="tpl_options" name="id" style="width:300px;"> <option value="">— Select Setting Group —</option> <?php - foreach($template->getGroup()->getTemplates() as $cn=>$t) { + foreach($group->getTemplates() as $cn=>$t) { $nfo=$t->getDescription(); if (!$nfo['name']) continue; @@ -41,6 +49,10 @@ $tpl=$msgtemplates[$info['tpl']]; echo sprintf('<option value="%s" %s>%s</option>', $t->getId(),$sel,$nfo['name']); } + if ($id == 0) { ?> + <option selected="selected" value="<?php echo $id; ?>"><?php + echo $msgtemplates[$selected]['name']; ?></option> + <?php } ?> </select> <input type="submit" value="Go"> diff --git a/main.inc.php b/main.inc.php index e5d19c0a417ce78d71ccfdb5b3a1772bff9ec042..062c1d139c52ecee11cb000fa5d16afd03160631 100644 --- a/main.inc.php +++ b/main.inc.php @@ -70,6 +70,7 @@ define('SETUP_DIR',INCLUDE_DIR.'setup/'); define('UPGRADE_DIR', INCLUDE_DIR.'upgrader/'); + define('I18N_DIR', INCLUDE_DIR.'i18n/'); /*############## Do NOT monkey with anything else beyond this point UNLESS you really know what you are doing ##############*/ diff --git a/scp/login.php b/scp/login.php index fcefaafd666660cdaf2d44c5e32e4b0bcfaeab37..2f3cf2236e9f4996bb10b94764fb6d0a14d99d22 100644 --- a/scp/login.php +++ b/scp/login.php @@ -24,7 +24,7 @@ $msg = $_SESSION['_staff']['auth']['msg']; $msg = $msg?$msg:'Authentication Required'; if($_POST) { //$_SESSION['_staff']=array(); #Uncomment to disable login strikes. - if(($user=Staff::login($_POST['username'], $_POST['passwd'], $errors))){ + if(($user=Staff::login($_POST['userid'], $_POST['passwd'], $errors))){ $dest=($dest && (!strstr($dest,'login.php') && !strstr($dest,'ajax.php')))?$dest:'index.php'; @header("Location: $dest"); require_once('index.php'); //Just incase header is messed up. diff --git a/scp/pwreset.php b/scp/pwreset.php new file mode 100644 index 0000000000000000000000000000000000000000..a8efb2f6ef86715463398d01143d907a0313e10e --- /dev/null +++ b/scp/pwreset.php @@ -0,0 +1,88 @@ +<?php +/********************************************************************* + pwreset.php + + Handles step 2, 3 and 5 of password resetting + 1. Fail to login (2+ fail login attempts) + 2. Visit password reset form and enter username or email + 3. Receive an email with a link and follow it + 4. Visit password reset form again, with the link + 5. Enter the username or email address again and login + 6. Password change is now required, user changes password and + continues on with the session + + Peter Rotich <peter@osticket.com> + Jared Hancock <jared@osticket.com> + Copyright (c) 2006-2013 osTicket + http://www.osticket.com + + Released under the GNU General Public License WITHOUT ANY WARRANTY. + See LICENSE.TXT for details. + + vim: expandtab sw=4 ts=4 sts=4: +**********************************************************************/ +require_once('../main.inc.php'); +if(!defined('INCLUDE_DIR')) die('Fatal Error. Kwaheri!'); + +require_once(INCLUDE_DIR.'class.staff.php'); +require_once(INCLUDE_DIR.'class.csrf.php'); + +$tpl = 'pwreset.php'; +if($_POST) { + if (!$ost->checkCSRFToken()) { + Http::response(400, 'Valid CSRF Token Required'); + exit; + } + switch ($_POST['do']) { + case 'sendmail': + if (($staff=Staff::lookup($_POST['userid']))) { + if (!$staff->sendResetEmail()) { + $tpl = 'pwreset.sent.php'; + } + } + else + $msg = 'Unable to verify username ' + .Format::htmlchars($_POST['userid']); + break; + case 'newpasswd': + // TODO: Compare passwords + $tpl = 'pwreset.login.php'; + $_config = new Config('pwreset'); + if (($staff = new StaffSession($_POST['userid'])) && + !$staff->getId()) + $msg = 'Invalid user-id given'; + elseif (!($id = $_config->get($_POST['token'])) + || $id != $staff->getId()) + $msg = 'Invalid reset token'; + elseif (!($ts = $_config->lastModified($_POST['token'])) + && ($ost->getConfig()->getPwResetWindow() < (time() - strtotime($ts)))) + $msg = 'Invalid reset token'; + elseif (!$staff->forcePasswdRest()) + $msg = 'Unable to reset password'; + else { + Staff::_do_login($staff, $_POST['userid']); + $_SESSION['_staff']['reset-token'] = $_POST['token']; + header('Location: index.php'); + exit(); + } + break; + } +} +elseif ($_GET['token']) { + $msg = 'Re-enter your username or email'; + $_config = new Config('pwreset'); + if (($id = $_config->get($_GET['token'])) + && ($staff = Staff::lookup($id))) + $tpl = 'pwreset.login.php'; + else + header('Location: index.php'); +} +elseif ($cfg->allowPasswordReset()) { + $msg = 'Enter your username or email address below'; +} +else { + $_SESSION['_staff']['auth']['msg']='Password resets are disabled'; + return header('Location: index.php'); +} +define("OSTSCPINC",TRUE); //Make includes happy! +include_once(INCLUDE_DIR.'staff/'. $tpl); diff --git a/scp/staff.inc.php b/scp/staff.inc.php index 577fdd12c6a124a0d98dd50b792d080ab40f983e..503c3cd413be64319882c255f9595029cbd0615d 100644 --- a/scp/staff.inc.php +++ b/scp/staff.inc.php @@ -1,7 +1,7 @@ <?php /************************************************************************* staff.inc.php - + File included on every staff page...handles logins (security) and file path issues. Peter Rotich <peter@osticket.com> @@ -42,13 +42,14 @@ require_once(INCLUDE_DIR.'class.nav.php'); require_once(INCLUDE_DIR.'class.csrf.php'); /* First order of the day is see if the user is logged in and with a valid session. - * User must be valid staff beyond this point + * User must be valid staff beyond this point * ONLY super admins can access the helpdesk on offline state. */ if(!function_exists('staffLoginPage')) { //Ajax interface can pre-declare the function to trap expired sessions. function staffLoginPage($msg) { + global $ost, $cfg; $_SESSION['_staff']['auth']['dest']=THISURI; $_SESSION['_staff']['auth']['msg']=$msg; require(SCP_DIR.'login.php'); @@ -59,7 +60,15 @@ if(!function_exists('staffLoginPage')) { //Ajax interface can pre-declare the fu $thisstaff = new StaffSession($_SESSION['_staff']['userID']); //Set staff object. //1) is the user Logged in for real && is staff. if(!$thisstaff || !is_object($thisstaff) || !$thisstaff->getId() || !$thisstaff->isValid()){ - $msg=(!$thisstaff || !$thisstaff->isValid())?'Authentication Required':'Session timed out due to inactivity'; + if (isset($_SESSION['_staff']['auth']['msg'])) { + $msg = $_SESSION['_staff']['auth']['msg']; + unset($_SESSION['_staff']['auth']['msg']); + } + elseif ($thisstaff && !$thisstaff->isValid()) + $msg = 'Session timed out due to inactivity'; + else + $msg = 'Authentication Required'; + staffLoginPage($msg); exit; } @@ -88,7 +97,7 @@ if ($_POST && !$ost->checkCSRFToken()) { exit; } -//Add token to the header - used on ajax calls [DO NOT CHANGE THE NAME] +//Add token to the header - used on ajax calls [DO NOT CHANGE THE NAME] $ost->addExtraHeader('<meta name="csrf_token" content="'.$ost->getCSRFToken().'" />'); /******* SET STAFF DEFAULTS **********/ diff --git a/setup/test/tests/class.test.php b/setup/test/tests/class.test.php index 542d47c353cab0314028f77a461d80b02cc34bfb..a9da96b2d50e547eebd768b58de21df2a53c30be 100644 --- a/setup/test/tests/class.test.php +++ b/setup/test/tests/class.test.php @@ -9,6 +9,7 @@ class Test { '/include/htmLawed.php', '/include/PasswordHash.php', '/include/pear/', + '/include/Spyc.php', ); function Test() {