diff --git a/application/classes/auth/orm.php b/application/classes/auth/orm.php new file mode 100644 index 0000000..06fe9cb --- /dev/null +++ b/application/classes/auth/orm.php @@ -0,0 +1,31 @@ +loaded(); + } + + public function logged_in($role = NULL, $all_required = TRUE) { + return FALSE !== $this->get_user(); + } +} +?> diff --git a/application/classes/controller/welcome.php b/application/classes/controller/welcome.php index e3b27d6..0a60a6c 100644 --- a/application/classes/controller/welcome.php +++ b/application/classes/controller/welcome.php @@ -1,10 +1,16 @@ -request->response = 'hello, world!'; + Block::add(array( + 'title'=>sprintf('%s: %s (%s)',_('Server'),TSM::name(),TSM::version()), + 'body'=>'hello, world!', + )); + + $this->template->content = Block::factory(); } } // End Welcome +?> diff --git a/application/classes/database/exception.php b/application/classes/database/exception.php new file mode 100644 index 0000000..cae0dcf --- /dev/null +++ b/application/classes/database/exception.php @@ -0,0 +1,13 @@ + diff --git a/application/classes/database/tsm.php b/application/classes/database/tsm.php new file mode 100644 index 0000000..65b7eeb --- /dev/null +++ b/application/classes/database/tsm.php @@ -0,0 +1,333 @@ +_connection) + return; + + // Extract the connection parameters, adding required variabels + extract($this->_config['connection'] + array( + 'database' => '', + 'hostname' => '', + 'username' => '', + 'password' => '', + 'persistent' => FALSE, + )); + + // Get user login details from user session - these are set by login + if (! $username) + $username = Session::instance()->get_once('admin_name'); + if (! $password) + $password = Session::instance()->get_once('password'); + + // Prevent this information from showing up in traces + unset($this->_config['connection']['username'], $this->_config['connection']['password']); + + if (! file_exists(Kohana::config('config.client'))) { + system_message(array('title'=>'Cant find DSMADMC', + 'body'=>sprintf('Unable to find the dsmadmc at %s',Kohana::config('client.config')), + 'type'=>'error')); + + return FALSE; + } + + if (! $username OR ! $password) + Request::instance()->redirect('/login?need_login=1'); + + try + { + if ($persistent) + { + // Create a persistent connection + throw new Kohana_Exception('Cant do persistant connections'); + } + else + { + // Create a connection and force it to be a new link + $this->_connection = sprintf('%s -id=%s -password=%s -displ=list -dataonly=YES %s %s', + Kohana::config('config.client'), + $username, + $password, + Kohana::config('config.client_errorlogname') ? sprintf('-errorlogname=%s',Kohana::config('config.client_errorlogname')) : '', + Kohana::config('config.client_stanza') ? sprintf('-se=%s',Kohana::config('config.client_stanza')) : '' + ); + + $result = $this->query(Database::SELECT,'SELECT server_name,platform,version,release,level,sublevel FROM status'); + +//echo Kohana::debug($result);die(); + if ($result) + return TSM::instance()->set($username,$password,$result); + else + Request::instance()->redirect(Request::instance()->uri()); + } + } + catch (ErrorException $e) + { + // No connection exists + $this->_connection = NULL; + + throw new Database_Exception(':error', array( + ':error' => sprintf('%s error in %s (%s)',$e->getMessage(),$e->getFile(),$e->getLine()), + ), + $e->getCode()); + } + + // \xFF is a better delimiter, but the PHP driver uses underscore + $this->_connection_id = sha1($hostname.'_'.$username.'_'.$password); + + if ( ! empty($this->_config['charset'])) + { + // Set the character set + $this->set_charset($this->_config['charset']); + } + } + + public function disconnect() + { + try + { + // Database is assumed disconnected + $status = TRUE; + + if (is_resource($this->_connection)) + { + if ($status = mysql_close($this->_connection)) + { + // Clear the connection + $this->_connection = NULL; + } + } + } + catch (Exception $e) + { + // Database is probably not disconnected + $status = ! is_resource($this->_connection); + } + + return $status; + } + + public function set_charset($charset) + { + // Make sure the database is connected + $this->_connection or $this->connect(); + + // Nothing to do for TSM + $status = TRUE; + + if ($status === FALSE) + { + throw new Database_Exception(':error', + array(':error' => mysql_error($this->_connection)), + mysql_errno($this->_connection)); + } + } + + public function query($type, $sql, $as_object = FALSE, array $params = NULL) + { + // Make sure the database is connected + $this->_connection or $this->connect(); + + if ( ! empty($this->_config['profiling'])) + { + // Benchmark this query for the current instance + $benchmark = Profiler::start("Database ({$this->_instance})", $sql); + } + + // Execute the query + $stderr = exec($this->_connection.'"'.$sql.'"',$stdout,$rc); + + // Work out our message codes + $result = array(); + foreach ($stdout as $line) + if (! preg_match('/^('.$this->msg_format.')\s+/',$line,$matches)) + array_push($result,$line); + elseif (! in_array($matches[1],$this->ignore_codes)) + array_push($this->_query_msg_codes,$matches[1]); + + // If we got a no data code + if (array_intersect($this->_query_msg_codes,$this->nodata_codes)) { + $result = array(0); + $rc = 0; + } + + if ($stderr AND $rc) { + if (isset($benchmark)) + { + // This benchmark is worthless + Profiler::delete($benchmark); + } + + SystemMessage::TSM_Error($stdout,$sql); + return FALSE; + } + + if (isset($benchmark)) + { + Profiler::stop($benchmark); + } + + // Set the last query + $this->last_query = $sql; + + if ($type === Database::SELECT) + { + // Return an iterator of results + return new Database_TSM_Result($result, $sql, $as_object, $params); + } + elseif ($type === Database::INSERT) + { + throw new Kohana_Exception('Database INSERTS are not supported'); + } + } + + public function list_tables($like = NULL) + { + if (is_string($like)) + { + // Search for table names + $result = $this->query(Database::SELECT, 'SHOW TABLES LIKE '.$this->quote($like), FALSE); + } + else + { + // Find all table names + $result = $this->query(Database::SELECT, 'SHOW TABLES', FALSE); + } + + $tables = array(); + foreach ($result as $row) + { + $tables[] = reset($row); + } + + return $tables; + } + + public function list_columns($table, $like = NULL, $add_prefix = TRUE) + { + // Quote the table name + $table = ($add_prefix === TRUE) ? $this->quote_table($table) : $table; + + if (is_string($like)) + { + // Search for column names + $result = $this->query(Database::SELECT, 'SHOW FULL COLUMNS FROM '.$table.' LIKE '.$this->quote($like), FALSE); + } + else + { + // Find all column names + $result = $this->query(Database::SELECT, sprintf('SELECT * FROM SYSCAT.COLUMNS WHERE TABNAME=\'%s\'',$table), FALSE); + } + + $count = 0; + $columns = array(); + foreach ($result as $row) + { + list($type, $length) = $this->_parse_type($row['TYPENAME']); + + $column = $this->datatype($type); + + $column['column_name'] = $row['COLNAME']; +// $column['column_default'] = $row['Default']; + $column['data_type'] = $type; + $column['is_nullable'] = ($row['NULLS'] == 'TRUE'); + $column['ordinal_position'] = ++$count; + + switch (strtolower($column['data_type'])) + { + case 'float': + if (isset($length)) + { + list($column['numeric_precision'], $column['numeric_scale']) = explode(',', $length); + } + break; + case 'int': + if (isset($length)) + { + // MySQL attribute + $column['display'] = $length; + } + break; + case 'string': + switch ($column['data_type']) + { + case 'binary': + case 'varbinary': + $column['character_maximum_length'] = $length; + break; + case 'char': + case 'varchar': + $column['character_maximum_length'] = $length; + case 'text': + case 'tinytext': + case 'mediumtext': + case 'longtext': + $column['collation_name'] = $row['Collation']; + break; + case 'enum': + case 'set': + $column['collation_name'] = $row['Collation']; + $column['options'] = explode('\',\'', substr($length, 1, -1)); + break; + } + break; + } + + // TSM attributes + $column['comment'] = $row['REMARKS']; + + $columns[$row['COLNAME']] = $column; + } + + return $columns; + } + + public function escape($value) + { + // Make sure the database is connected + $this->_connection or $this->connect(); + + // SQL standard is to use single-quotes for all values + return "'$value'"; + } + +} // End Database_TSM diff --git a/application/classes/database/tsm/result.php b/application/classes/database/tsm/result.php new file mode 100644 index 0000000..80891af --- /dev/null +++ b/application/classes/database/tsm/result.php @@ -0,0 +1,85 @@ +_internal_row++; + continue; + } + + list($k,$v) = explode(':',$line,2); + + $this->_rows[$this->_internal_row][trim($k)] = trim($v); + } + + $this->_total_rows = $this->_internal_row; + $this->_internal_row = 0; + } + + public function __destruct() + { + return; + } + + public function seek($offset) + { + if ($this->offsetExists($offset)) + { + // Set the current row to the offset + $this->_current_row = $this->_internal_row = $offset; + + return TRUE; + } + else + { + return FALSE; + } + } + + public function current() + { + if ($this->_current_row !== $this->_internal_row AND ! $this->seek($this->_current_row)) + return FALSE; + + // Increment internal row for optimization assuming rows are fetched in order + $this->_internal_row++; + + if ($this->_as_object === TRUE) + { + // Return an stdClass + return $this; + } + elseif (is_string($this->_as_object)) + { + // Return an object of given class name + $o = new $this->_as_object; + + return $o->_load_values($this->_rows[$this->_current_row++]); + } + else + { + // Return an array of the row + return $this->_rows[$this->_current_row++]; + } + } + +} // End Database_TSM_Result +?> diff --git a/application/classes/model/account.php b/application/classes/model/account.php new file mode 100644 index 0000000..51604b3 --- /dev/null +++ b/application/classes/model/account.php @@ -0,0 +1,17 @@ + diff --git a/application/classes/model/auth/userdefault.php b/application/classes/model/auth/userdefault.php new file mode 100644 index 0000000..37cbcf8 --- /dev/null +++ b/application/classes/model/auth/userdefault.php @@ -0,0 +1,91 @@ + array( + 'not_empty' => NULL, + 'min_length' => array(4), + 'max_length' => array(8), + ), + 'password' => array( + 'not_empty' => NULL, + 'min_length' => array(5), + 'max_length' => array(16), + ), + 'password_confirm' => array( + 'matches_ifset' => array('password'), + ), + ); + + // Columns to ignore + protected $_ignored_columns = array('password_confirm'); + + // Field labels + protected $_labels = array( + 'admin_name' => 'username', + 'email' => 'email address', + 'password' => 'password', + 'password_confirm' => 'password confirmation', + ); + + /** + * Validates login information from an array, and optionally redirects + * after a successful login. + * + * @param array values to check + * @param string URI or URL to redirect to + * @return boolean + */ + public function login(array & $array, $redirect = FALSE) { + $fieldname = 'admin_name'; + $array = Validate::factory($array) + ->label('admin_name', $this->_labels[$fieldname]) + ->label('password', $this->_labels['password']) + ->filter(TRUE, 'trim') + ->filter('admin_name','strtoupper') + ->rules('admin_name', $this->_rules[$fieldname]) + ->rules('password', $this->_rules['password']); + + // Get the remember login option + $remember = isset($array['remember']); + + // Login starts out invalid + $status = FALSE; + + if ($array->check()) + { + // Attempt to load the user + $this->where($fieldname, '=', $array['admin_name'])->find(); + + if ($this->loaded() AND Auth::instance()->login($this, $array['password'], $remember)) + { + if (is_string($redirect)) + { + // Redirect after a successful login + Request::instance()->redirect($redirect); + } + + // Login is successful + $status = TRUE; + } + else + { + $array->error('admin_name', 'invalid'); + } + } + + return $status; + } +} +?> diff --git a/application/classes/orm.php b/application/classes/orm.php new file mode 100644 index 0000000..a365572 --- /dev/null +++ b/application/classes/orm.php @@ -0,0 +1,39 @@ +find_all() as $object) { + $this->_load_values($object->_object); + + return; + } + } + + /** + * We need to call our own _load_values() to support find() and TSM::result() + * + * (Since _load_values is protected, we need to make it public here, + * so that it can be called in TSM::result().) + * + * @see TSM::result + * @see ORM::find + */ + public function _load_values(array $values) { + return parent::_load_values($values); + } +} +?> diff --git a/application/classes/systemmessage.php b/application/classes/systemmessage.php index 6753b7c..5324687 100644 --- a/application/classes/systemmessage.php +++ b/application/classes/systemmessage.php @@ -1,4 +1,22 @@ _('Error while talking to TSM'), + 'body'=>_('Running SQL').': '.$sql.'

'.implode('
',$error), + 'type'=>'error', + )); + } +} ?> diff --git a/application/classes/tsm.php b/application/classes/tsm.php new file mode 100644 index 0000000..217b46b --- /dev/null +++ b/application/classes/tsm.php @@ -0,0 +1,37 @@ +set('admin_name',$username); + Session::instance()->set('password',$password); + + Session::instance()->set('SERVER',$server); + } + + public static function instance() { + return new TSM; + } + + public static function name() { + return Session::instance()->get('SERVER')->rewind()->get('SERVER_NAME'); + } + + public static function version() { + $s = Session::instance()->get('SERVER')->rewind()->as_array(); + $s = array_pop($s); + + return sprintf('%s.%s.%s.%s',$s['VERSION'],$s['RELEASE'],$s['LEVEL'],$s['SUBLEVEL']); + } +} +?> diff --git a/application/config/auth.php b/application/config/auth.php new file mode 100644 index 0000000..9be58f5 --- /dev/null +++ b/application/config/auth.php @@ -0,0 +1,29 @@ + 'ORM', + 'hash_method' => 'sha1', + 'salt_pattern' => '1, 3, 5, 9, 14, 15, 20, 21, 28, 30', + 'lifetime' => 1209600, + 'session_key' => 'admin_name', + 'forced_key' => 'auth_forced', + + // Username/password combinations for the Auth File driver + 'users' => array( + // 'admin' => 'b3154acf3a344170077d11bdb5fff31532f679a1919e716a02', + ), + +); +?> diff --git a/application/config/config.php b/application/config/config.php new file mode 100644 index 0000000..0a55a76 --- /dev/null +++ b/application/config/config.php @@ -0,0 +1,33 @@ + 'file', + 'client' => '/opt/tivoli/tsm/client/ba/bin/dsmadmc', + 'client_errorlogname' => '/tmp/pta-tsm-errorlog.log', + 'date_format' => 'd-m-Y', + 'email_admin_only'=> array( + 'method'=>array('wurley@users.sf.net'=>'Deon George'), + ), + 'method_directory'=> array( // Out method paths for the different functions + ), + 'method_security' => TRUE, // Enables Method Security. Setting to false means any method can be run without authentication + 'site' => array( + '172.31.9.4'=>1, + ), + 'site_debug' => FALSE, + 'site_mode' => array( + '172.31.9.4'=>Kohana::DEVELOPMENT, + 'phptsmadmin.sf.net'=>Kohana::PRODUCTION, + ) +); +?> diff --git a/application/config/database.php b/application/config/database.php new file mode 100644 index 0000000..7affc80 --- /dev/null +++ b/application/config/database.php @@ -0,0 +1,43 @@ + array + ( + 'type' => 'tsm', + 'connection' => array( + /** + * The following options are available for MySQL: + * + * string hostname server hostname, or socket + * string database database name + * string username database username + * string password database password + * boolean persistent use persistent connections? + * + * Ports and sockets may be appended to the hostname. + */ + 'hostname' => 'localhost', + 'database' => 'kohana', + 'username' => FALSE, + 'password' => FALSE, + 'persistent' => FALSE, + ), + 'table_prefix' => '', + 'charset' => 'utf8', + 'caching' => FALSE, + 'profiling' => TRUE, + ), +); +?>