diff --git a/include/class-Mailbox.php b/include/class-Mailbox.php index 6c77b9d..8e9a171 100644 --- a/include/class-Mailbox.php +++ b/include/class-Mailbox.php @@ -1,99 +1,108 @@ . /** * A mailbox */ class Mailbox extends Domain { const T = 'mailbox'; public function __construct() { $this->booleans( 'mailbox_receive' ); } + /** + * Get the mailbox username + * + * @return string + */ + public function getMailboxUsername() { + return $this->get( 'mailbox_username' ); + } + /** * Get the mailbox address * * @return string E-mail */ public function getMailboxAddress() { return sprintf( "%s@%s", $this->get( 'mailbox_username' ), $this->get( 'domain_name' ) ); } /** * Get the mailbox permalink * * @return string */ public function getMailboxPermalink( $absolute = false ) { return Mailbox::permalink( $this->get( 'domain_name' ), $this->get( 'mailbox_username' ) ); } /** * Update this mailbox password * * @param $password string * @return string */ public function updateMailboxPassword( $password = null ) { if( ! $password ) { $password = generate_password(); } $enc_password = Mailbox::encryptPassword( $password ); query_update( 'mailbox', [ new DBCol( 'mailbox_password', $enc_password, 's' ), ], sprintf( "domain_ID = %d AND mailbox_username = '%s'", $this->getDomainID(), esc_sql( $this->get( 'mailbox_username' ) ) ) ); return $password; } /** * Get the mailbox permalink * * @param $domain string * @param $mailbox string * @param $absolute boolean * @return string */ public static function permalink( $domain, $mailbox = null, $absolute = false ) { $part = site_page( 'mailbox.php', $absolute ) . _ . $domain; if( $mailbox ) { $part .= _ . $mailbox; } return $part; } /** * Encrypt a password * * TODO: do not hardcode to my Dovecot configuration */ public static function encryptPassword( $password ) { $salt = bin2hex( openssl_random_pseudo_bytes( 3 ) ); return '{SHA512-CRYPT}' . crypt( $password, "$6$$salt" ); } } diff --git a/include/class-MailboxAPI.php b/include/class-MailboxAPI.php index fdc94c8..822fa2a 100644 --- a/include/class-MailboxAPI.php +++ b/include/class-MailboxAPI.php @@ -1,54 +1,64 @@ . /** * Mailbox API */ class MailboxAPI extends DomainAPI { public function __construct() { Query::__construct(); $this->from( Mailbox::T ); $this->defaultClass( 'Mailbox' ); } + /** + * Where the Mailbox is Active (or not) + * + * @param boolean $active If you want the active, or the inactive + * @return self + */ + public function whereMailboxIsActive( $active = true ) { + return $this->wheerInt( 'mailbox_active', $active ); + } + /** * Join mailboxes and domain (once) * * @return self */ public function joinMailboxDomain() { if( empty( $this->joinedMailboxDomain ) ) { $this->from( 'domain' ); $this->equals( 'domain.domain_ID', 'mailbox.domain_ID' ); $this->joinedMailboxDomain = true; } return $this; } /** * Check if I can edit this mailbox * * Actually it just checks if you can edit the whole domain. * * @return boolean */ public function whereMailboxIsEditable() { return $this->whereDomainIsEditable(); } } diff --git a/include/class-User.php b/include/class-User.php new file mode 100644 index 0000000..ebce5c4 --- /dev/null +++ b/include/class-User.php @@ -0,0 +1,162 @@ +. + +trait UserTrait { + + /** + * Normalize a User object + */ + protected function normalizeUser() { + $this->integers( 'user_ID' ); + } + + /** + * Get the user ID + * + * @return int + */ + public function getUserID() { + return $this->get( 'user_ID' ); + } + + /** + * Get the user first name + * + * @return string + */ + public function getUserName() { + return $this->get( 'user_name' ); + } + + /** + * Get the user surname + * + * @return string + */ + public function getUserSurname() { + return $this->get( 'user_surname' ); + } + + /** + * Get the user UID + * + * @return string + */ + public function getUserUID() { + return $this->get( 'user_uid' ); + } + + /** + * Get the user E-mail + * + * @return string + */ + public function getUserEmail() { + return $this->get( 'user_email' ); + } + + /** + * Get the role of this user + * + * @return string + */ + public function getUserRole() { + return $this->get( 'user_role' ); + } + + /** + * Get the human name of the user role + * + * @return string + */ + public function getUserRoleLabel() { + $roles = User::roles(); + $role = $this->getUserRole(); + return $roles[ $role ]; + } + + /** + * Check if this user is me + * + * @return boolean + */ + public function isUserMyself() { + $id = $this->getUserID(); + return is_logged() && get_user()->getUserID() === $id; + } + + /** + * Check if I can edit this user + * + * @return boolean + */ + public function isUserEditable() { + return $this->isUserMyself() || has_permission( 'edit-user-all' ); + } + + /** + * Get the domain edit URl + * + * @param boolean $absolute True for an absolute URL + * @return string + */ + public function getUserPermalink( $absolute = false ) { + return User::permalink( $this->getUserUID(), $absolute ); + } +} + +/** + * A mailbox + */ +class User extends Sessionuser { + + use UserTrait; + + /** + * Constructor + */ + public function __construct() { + $this->normalizeUser(); + } + + /** + * Get the known user roles + * + * @return array + */ + public static function roles() { + return [ + 'user' => __( "User" ), + 'admin' => __( "Admin" ), + ]; + } + + /** + * Get the User permalink + * + * @param string $user User UID + * @param boolean $absolute + * @return string + */ + public static function permalink( $user = null, $absolute = false ) { + $part = site_page( 'user.php', $absolute ); + if( $user ) { + $part .= _ . $user; + } + return $part; + } +} diff --git a/include/class-UserAPI.php b/include/class-UserAPI.php index 1195e8c..2aa663a 100644 --- a/include/class-UserAPI.php +++ b/include/class-UserAPI.php @@ -1,93 +1,103 @@ . /** * User users API */ class UserAPI extends DomainAPI { /** * Column name of the User ID */ const USER_ID = 'user.user_ID'; /** * Constructor */ public function __construct() { Query::__construct(); $this->from( User::T ); - $this->defaultClass( 'User' ); + $this->defaultClass( User::class ); } /** * Filter to a certain User UID * * @param string $uid User UID * @return self */ public function whereUserUID( $uid ) { return $this->whereStr( 'user_uid', $uid ); } + /** + * Filter to a certain User E-mail + * + * @param string $email User E-mail + * @return self + */ + public function whereUserEmail( $email ) { + return $this->whereStr( 'user_email', $email ); + } + /** * Filter to a certain User ID * * @param string $uid User ID * @return self */ public function whereUserID( $id ) { return $this->whereInt( static::USER_ID, $id ); } /** * Filter to myself * * @return self */ public function whereUserIsMe() { - $id = get_user()->getSessionuserID(); + $id = get_user()->getUserID(); return $this->whereUserID( $id ); } /** * WHere the User(s) is editable * * @return Query */ public function whereUserIsEditable() { // if I can't see everyone, just see myself - if( !has_permission( 'edit-all-users' ) ) { + if( !has_permission( 'edit-user-all' ) ) { $this->whereUserIsMe(); } return $this; } /** * Limit to a specific User * * @param object $user User * @return self */ public function whereUser( $user ) { - $id = $user->whereSessionuserID(); + $id = $user->getSessionuserID(); return $this->whereUserID( $id ); } } diff --git a/include/class-UserPager.php b/include/class-UserPager.php index 7853551..3a33eff 100644 --- a/include/class-UserPager.php +++ b/include/class-UserPager.php @@ -1,61 +1,79 @@ . /** * User users API */ class UserPager extends QueryPager { /** * Constructor * * @param $data array */ public function __construct( $data = [] ) { parent::__construct(); if( isset( $data['uid'] ) ) { $data['uid'] = luser_input( $data['uid'], 32 ); $this->setArg( 'uid', $data['uid'] ); } + + if( isset( $data['email'] ) ) { + $data['email'] = luser_input( $data['email'], 32 ); + $this->setArg( 'email', $data['email'] ); + } } /** * Create a Query for User(s) * * @return Query */ public function createQuery() { $query = new UserAPI(); $query->whereUserIsEditable(); + // search by login + $uid = $this->getArg( 'uid' ); + $email = $this->getArg( 'email' ); + + if( $uid ) { + $query->whereUserUID( $uid ); + } + + if( $email ) { + $query->whereUserEmail( $email ); + } + return $query; } /** * Eventually apply an order * * @override */ public function applyOrder( & $query, $order_by, $direction ) { + $query->orderBy( 'user_uid' ); } } diff --git a/load-post.php b/load-post.php index 3aa7734..ad0be38 100644 --- a/load-post.php +++ b/load-post.php @@ -1,86 +1,101 @@ . // include path define_default( 'INCLUDE_PATH', ABSPATH . __ . 'include' ); // template path define_default( 'TEMPLATE_PATH', ABSPATH . __ . 'template' ); // autoload classes from the /include directory spl_autoload_register( function( $name ) { $path = INCLUDE_PATH . __ . "class-$name.php"; if( is_file( $path ) ) { require $path; } } ); +// override default user class +define( 'SESSIONUSER_CLASS', 'User' ); + // load common functions require INCLUDE_PATH . __ . 'functions.php'; // jquery URL // provided by the libjs-jquery package as default define_default( 'JQUERY_URL', '/javascript/jquery/jquery.min.js' ); // Bootstrap CSS/JavaScript files without trailing slash // provided by the libjs-bootstrap package as default define_default( 'BOOTSTRAP_DIR_URL', '/javascript/bootstrap' ); // path to the Net SMTP class // provided by the php-net-smtp package as default define_default( 'NET_SMTP', '/usr/share/php/Net/SMTP.php' ); // register JavaScript/CSS files register_js( 'jquery', JQUERY_URL ); register_js( 'bootstrap', BOOTSTRAP_DIR_URL . '/js/bootstrap.min.js' ); register_css( 'bootstrap', BOOTSTRAP_DIR_URL . '/css/bootstrap.min.css' ); register_css( 'custom-css', ROOT . '/content/style.css' ); // GNU Gettext i18n define( 'GETTEXT_DOMAIN', 'reyboz-hosting-panel' ); define( 'GETTEXT_DIRECTORY', 'l10n' ); define( 'GETTEXT_DEFAULT_ENCODE', CHARSET ); // UTF-8 // common strings define_default( 'SITE_NAME', "Boz Libre Hosting Panel" ); define_default( 'CONTACT_EMAIL', 'support@' . DOMAIN ); define_default( 'REPO_URL', 'https://github.com/valerio-bozzolan/boz-libre-hosting-panel' ); // limit session duration to 5 minutes (60s * 100m) define_default( 'SESSION_DURATION', 6000 ); +/** + * Mailbox base path + * + * Used by CLI scripts to calculate the current quotas. + * + * The mailboxes should have paths like: + * MAILBOX_BASE_PATH/domain_name/user_name/ + */ +define_default( 'MAILBOX_BASE_PATH', '/home/vmail' ); + // register web pages add_menu_entries( [ - new MenuEntry( 'index', '/', __( "Dashboard" ) ), - new MenuEntry( 'login', 'login.php', __( "Login" ) ), - new MenuEntry( 'profile', 'profile.php', __( "Profile" ) ), - new MenuEntry( 'logout', 'logout.php', __( "Logout" ) ), - new MenuEntry( 'password-reset', 'password-reset.php', __( "Password reset" ) ), + new MenuEntry( 'index', '/', __( "Dashboard" ), null, 'backend' ), + new MenuEntry( 'login', 'login.php', __( "Login" ) ), + new MenuEntry( 'profile', 'profile.php', __( "Profile" ) ), + new MenuEntry( 'logout', 'logout.php', __( "Logout" ), null, 'read' ), + new MenuEntry( 'user-list', 'user-list.php', __( "Users" ), null, 'edit-user-all' ), + new MenuEntry( 'password-reset', 'password-reset.php', __( "Password reset" ) ), ] ); -// permissions +// permissions of a normal user register_permissions( 'user', [ 'read', 'backend', ] ); +// permissions of an admin inherit_permissions( 'admin', 'user', [ 'edit-user-all', 'edit-email-all', 'edit-domain-all', 'edit-ftp-all', ] ); diff --git a/template/user.php b/template/user.php new file mode 100644 index 0000000..3df0bce --- /dev/null +++ b/template/user.php @@ -0,0 +1,60 @@ +. + +/* + * This is the template for an User + * + * Called from: + * user.php + * + * Available variables: + * $user object|null + * $new_password string|null + */ + +// unuseful when load directly +defined( 'BOZ_PHP' ) or die; +?> + +
+ + +
+ + getUserEmail() ) : '' ?> class="form-control" /> +
+
+ + getUserUID() ) : '' ?> class="form-control" /> +
+ +
+ + +
+
+

+ + +
+ + +

+ /> + +
+ diff --git a/www/index.php b/www/index.php index 42d8e3c..3c239f6 100644 --- a/www/index.php +++ b/www/index.php @@ -1,80 +1,84 @@ . /* * This is the homepage of your hosting panel */ // load framework require '../load.php'; -// require read permissions -require_permission( 'read' ); - // spawn header Header::spawn( [ 'breadcrumb' => false, ] ); // user domains $domains = ( new DomainAPI() ) ->select( [ 'domain.domain_ID', 'domain_name', 'domain_active', ] ) ->whereDomainIsEditable() ->orderBy( 'domain_name' ) ->queryGenerator(); ?>

valid() ): ?>

+ +

getURL(), + __( "Users" ) + ) ?>

+ + . + +/* + * This is the domain edit page + */ + +// load framework +require '../load.php'; + +// spawn header +Header::spawn( [ + 'title' => __( "Users" ), +] ); + +$pager = new UserPager(); +?> + +
+

+ + getArg( 'email' ) ) ?> /> +

+

+ + getArg( 'uid' ) ) ?> /> +

+

+ +

+
+ + + + + + + + + + + + createPagedQuery()->queryGenerator() as $user ): ?> + + + + + + + + +
getUserPermalink(), + $user->getUserUID() + ) ?>getUserSurname() ) ?>getUserName() ) ?>getUserRoleLabel() ) ?>
+ +. + +/* + * This is the single User creation/edit page + */ + +// load framework +require '../load.php'; + +// wanted informations +$user = null; + +// URL paramenters (user_uid) +list( $user_uid ) = url_parts( 1, 0 ); + +// eventually retrieve mailforward from database +if( $user_uid ) { + $user = ( new UserAPI() ) + ->whereUserUID( $user_uid ) + ->whereUserIsEditable() + ->queryRow(); + + // 404 + if( !$user || !$user->isUserEditable() ) { + PageNotFound::spawn(); + } +} else { + // to create an FTP user, must edit all FTP users + require_permission( 'edit-user-all' ); +} + +// save destination action +if( is_action( 'user-save' ) ) { + +} + +// register action to generate a new password +$new_password = null; +if( is_action( 'change-password' ) && $user ) { + + // generate a new password and save + $new_password = generate_password(); + $encrypted = User::encryptPassword( $new_password ); + ( new UserAPI() ) + ->whereUser( $user ) + ->update( [ + new DBCol( User::PASSWORD, $encrypted, 's' ), + ] ); +} + +// spawn header +Header::spawn( [ + 'uid' => false, + 'title-prefix' => __( "User" ), + 'title' => $user + ? $user->getUserUID() + : __( "create" ), +] ); + +// spawn the page content +template( 'user', [ + 'user' => $user, + 'new_password' => $new_password, +] ); + +// spawn the footer +Footer::spawn();