diff --git a/database.sql b/database.sql index da5062c..3cf77b4 100644 --- a/database.sql +++ b/database.sql @@ -6,6 +6,9 @@ BEGIN; -- Tables from the main database structure \i database/create-tables.sql +-- User functions +\i database/users-functions.sql + -- Items tree management and associated functions \i database/items-tree-triggers.sql \i database/items-functions.sql diff --git a/database/users-functions.sql b/database/users-functions.sql new file mode 100644 index 0000000..e024b5f --- /dev/null +++ b/database/users-functions.sql @@ -0,0 +1,21 @@ +-- +-- Create a new user +-- + +CREATE OR REPLACE FUNCTION users_add( _email TEXT , _salt TEXT , _iters INT , _hash TEXT ) + RETURNS INT + LANGUAGE PLPGSQL + STRICT VOLATILE SECURITY INVOKER +AS $users_add$ +BEGIN + INSERT INTO users ( user_email , user_salt , user_iterations , user_hash ) + VALUES ( _email , _salt , _iters , _hash ); + RETURN 0; +EXCEPTION + WHEN unique_violation THEN + RETURN 1; +END; +$users_add$; + +REVOKE EXECUTE ON FUNCTION users_add( TEXT , TEXT , INT , TEXT ) FROM PUBLIC; +GRANT EXECUTE ON FUNCTION users_add( TEXT , TEXT , INT , TEXT) TO :webapp_user; diff --git a/includes/t-basics/dao_users.inc.php b/includes/t-basics/dao_users.inc.php new file mode 100644 index 0000000..4b98ef6 --- /dev/null +++ b/includes/t-basics/dao_users.inc.php @@ -0,0 +1,79 @@ + 0 ); + return $hash; + } + + + public function getUsers( ) + { + return $this->query( 'SELECT user_id , user_email FROM users ORDER BY LOWER( user_email )' )->execute( ); + } + + + public function getUser( $email ) + { + $query = $this->query( 'SELECT * FROM users WHERE user_email = LOWER( $1 )' ); + $results = $query->execute( $email ); + if ( empty( $results ) ) { + return null; + } + return array_shift( $results ); + } + + + public function checkLogin( $email , $password ) + { + $userData = $this->getUser( $email ); + if ( $userData != null ) { + $hashed = $this->hashPassword( $password , + $userData->user_salt , + $userData->user_iterations ); + if ( $hashed === $userData->user_hash ) { + return $userData; + } + } + return null; + } + + + public function addUser( $email , $password ) + { + $iterations = rand( 130 , 160 ); + + $randSource = array( ); + for ( $i = 0 ; $i < 26 ; $i ++ ) { + array_push( $randSource , chr( $i + ord( 'a' ) ) ); + array_push( $randSource , chr( $i + ord( 'A' ) ) ); + if ( $i < 10 ) { + array_push( $randSource , chr( $i + 48 ) ); + } + } + shuffle( $randSource ); + $salt = join( '' , array_splice( $randSource , 0 , 4 ) ); + + $hash = $this->hashPassword( $password , $salt , $iterations ); + + $result = $this->query( 'SELECT users_add( $1 , $2 , $3 , $4 ) AS error' ) + ->execute( $email , $salt , $iterations , $hash ); + return $result[ 0 ]->error; + } + + + public function hasUsers( ) + { + $result = $this->query( 'SELECT COUNT(*) AS n_users FROM users' )->execute( ); + return $result[0]->n_users; + } +} diff --git a/includes/t-basics/package.inc.php b/includes/t-basics/package.inc.php index 32df61b..3f51227 100644 --- a/includes/t-basics/package.inc.php +++ b/includes/t-basics/package.inc.php @@ -2,8 +2,8 @@ $package[ 'requires' ][] = 'form'; $package[ 'requires' ][] = 'hub-page'; -$package[ 'requires' ][] = 't-users'; +$package[ 'files' ][] = 'dao_users'; $package[ 'files' ][] = 'controllers'; $package[ 'files' ][] = 'pages'; @@ -20,3 +20,4 @@ $package[ 'pages' ][] = 'tasks_home'; $package[ 'pages' ][] = 'tasks_login'; $package[ 'pages' ][] = 'tasks_logout'; +$package[ 'daos' ][] = 'users'; diff --git a/includes/t-basics/pages.inc.php b/includes/t-basics/pages.inc.php index 1bb8453..7809136 100644 --- a/includes/t-basics/pages.inc.php +++ b/includes/t-basics/pages.inc.php @@ -14,6 +14,7 @@ abstract class AuthenticatedPage return array( 'items' => 'Items' , 'tasks' => 'Tasks' , + 'users' => 'Users' , 'logout' => 'Log out' ); } diff --git a/includes/t-users/package.inc.php b/includes/t-users/package.inc.php index db6fc56..7f732f0 100644 --- a/includes/t-users/package.inc.php +++ b/includes/t-users/package.inc.php @@ -1,5 +1,13 @@ 0 ); - return $hash; + parent::__construct( array( + '' => 'users_list' , + 'add' => 'users_add_form' , + ) ); + $this->setTitle( 'Users' ); + } +} + + +class Ctrl_UsersList + extends Controller +{ + + public function handle( Page $page ) + { + $dao = Loader::DAO( 'users' ); + return Loader::View( 'box' , null , Loader::View( 'users_list' , $dao->getUsers( ) ) ) + ->setClass( 'list' ) + ->addButton( BoxButton::create( 'Add user' , 'users/add' ) + ->setClass( 'list-add' ) ); + } + +} + + +class Ctrl_UsersAddForm + extends Controller +{ + public function handle( Page $page ) + { + return Loader::Create( 'Form' , 'Create user' , 'user-add' ) + ->addField( Loader::Create( 'Field' , 'email' , 'text' ) + ->setDescription( 'E-mail address:' ) + ->setValidator( Loader::Create( 'Validator_Email' , 'Invalid address.' ) ) ) + ->addField( Loader::Create( 'Field' , 'pass' , 'password' ) + ->setDescription( 'Password:' ) + ->setValidator( Loader::Create( 'Validator_StringLength' , 'This password' , 8 ) ) ) + ->addField( Loader::Create( 'Field' , 'pass2' , 'password' ) + ->setDescription( 'Confirm password:' ) ) + ->setURL( 'users' ) + ->addController( Loader::Ctrl( 'users_add' ) ) + ->controller( ); + } +} + + +class Ctrl_UsersAdd + extends Controller + implements FormAware +{ + private $form; + + public function setForm( Form $form ) + { + $this->form = $form; } - public function getUser( $email ) + public function handle( Page $page ) { - $query = $this->query( 'SELECT * FROM users WHERE user_email = LOWER( $1 )' ); - $results = $query->execute( $email ); - if ( empty( $results ) ) { + $p1 = $this->form->field( 'pass' ); + $p2 = $this->form->field( 'pass2' ); + if ( $p1->value( ) != $p2->value( ) ) { + $p1->putError( 'Passwords did not match.' ); return null; } - return array_shift( $results ); - } + $email = $this->form->field( 'email' ); + $error = Loader::DAO( 'users' )->addUser( $email->value( ) , + $p1->value( ) ); - public function checkLogin( $email , $password ) - { - $userData = $this->getUser( $email ); - if ( $userData != null ) { - $hashed = $this->hashPassword( $password , - $userData->user_salt , - $userData->user_iterations ); - if ( $hashed === $userData->user_hash ) { - return $userData; - } + switch ( $error ) { + + case 0: + return true; + + case 1: + $email->putError( 'This e-mail address is already in use.' ); + break; + + default: + $email->putError( 'Some unknown error has occurred (' . $error . ')' ); + break; } return null; } + +} + + + +class View_UsersList + extends BaseURLAwareView +{ + private $users; + + public function __construct( $users ) + { + $this->users = $users; + } + + public function render( ) + { + $table = HTML::make( 'table' ) + ->appendElement( HTML::make( 'tr' ) + ->setAttribute( 'class' , 'header' ) + ->appendElement( HTML::make( 'th' ) + ->appendText( 'E-mail address' ) ) ); + + foreach ( $this->users as $user ) { + $table->appendElement( HTML::make( 'tr' ) + ->appendElement( HTML::make( 'td' ) + ->appendText( $user->user_email ) ) ); + } + + return $table; + } }