diff --git a/.gitignore b/.gitignore index 269044e..865c090 100644 --- a/.gitignore +++ b/.gitignore @@ -1,31 +1,2 @@ -.DS_Store - -application/cache/* -!application/cache/index.html - -application/logs/* -!application/logs/index.html - -!application/*/.htaccess - -composer.lock - -user_guide_src/build/* -user_guide_src/cilexer/build/* -user_guide_src/cilexer/dist/* -user_guide_src/cilexer/pycilexer.egg-info/* -/vendor/ - -# IDE Files -#------------------------- -/nbproject/ -.idea/* - -## Sublime Text cache files -*.tmlanguage.cache -*.tmPreferences.cache -*.stTheme.cache -*.sublime-workspace -*.sublime-project -/tests/tests/ -/tests/results/ +nbproject/ +vendor/ diff --git a/.idea/Djerba_online_shopping.iml b/.idea/Djerba_online_shopping.iml new file mode 100644 index 0000000..576f36f --- /dev/null +++ b/.idea/Djerba_online_shopping.iml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..72d43fd --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..1eddd46 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,839 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + base + ../../ + emula + viewport + bug-work + name + .css + ico + tablet + Ui fea + src + avatar + glyphic + janux + clearf + Janux + mdl_perfectcontroller + lorem + + + <?php echo base_url(); ?> + mdl_items + + + + + + + + + + + true + DEFINITION_ORDER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + project + + + + + + + + + + + + + + + + project + + + true + + + + DIRECTORY + + false + + + + + + + + + + + + + + + + + 1512225012592 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..f5717c2 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2015 Kenji Suzuki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f04035c --- /dev/null +++ b/README.md @@ -0,0 +1,141 @@ +# CodeIgniter Deployer + +[![Latest Stable Version](https://poser.pugx.org/kenjis/codeigniter-deployer/v/stable)](https://packagist.org/packages/kenjis/codeigniter-deployer) [![Total Downloads](https://poser.pugx.org/kenjis/codeigniter-deployer/downloads)](https://packagist.org/packages/kenjis/codeigniter-deployer) [![Latest Unstable Version](https://poser.pugx.org/kenjis/codeigniter-deployer/v/unstable)](https://packagist.org/packages/kenjis/codeigniter-deployer) [![License](https://poser.pugx.org/kenjis/codeigniter-deployer/license)](https://packagist.org/packages/kenjis/codeigniter-deployer) + +A Deployment Tool for [CodeIgniter](https://github.com/bcit-ci/CodeIgniter) 3.0. + +You can deploy CodeIgniter with one command. + +This is based on [Deployer](http://deployer.org/) 3.0. + +## Folder Structure + +``` +codeigniter/ +└── deploy/ + ├── deploy.php ... config file for Deployer + ├── deploy.sh ... script to deploy + └── logs/ +``` + +## Requirements + +* PHP 5.4.0 or later +* Composer +* Git +* SSH +* Shell + +## Installation + +Install this project with Composer: + +~~~ +$ cd /path/to/codeigniter/ +$ composer require kenjis/codeigniter-deployer:1.0.x@dev --dev +~~~ + +Install `deploy` folder to your CodeIgniter application folder: + +~~~ +$ php vendor/kenjis/codeigniter-deployer/install.php +~~~ + +* Above command always overwrites exisiting files. +* You must run it at CodeIgniter project root folder. + +## Configuration + +Configure `deploy/deployer.php`. + +### Servers + +~~~php +// Your production server +server('prod', 'your.server.example.com', 22) + ->user('username') + ->forwardAgent() + ->stage('production') + ->env('branch', 'master') + ->env('deploy_path', '/var/www/your-codeigniter-app'); +~~~ + +See https://github.com/deployphp/docs/blob/master/servers.md for details. + +### Repository + +~~~php +// Your Git repository +set('repository', 'git@github.com:org/your-codeigniter-app.git'); +~~~ + +You need `git` command on your servers, and make sure you can `git clone` on the servers. + +### Reference + +* https://github.com/deployphp/docs + +## Configure Your Servers + +### Apache + +See the sample below. In this case, `/var/www/your-codeigniter-app` is a base folder. + +~~~ +your-codeigniter-app/ +├── current -> /var/www/your-codeigniter-app/releases/20150529181638 +└── releases/ +     ├── 20150529180505/ +     ├── 20150529181203/ +     └── 20150529181638/ +~~~ + +Each deployment is installed in `releases/YYYYMMDDHHMMSS` folder. + +The current release is `your-codeigniter-app/current` folder, and it is a symbolic link to `releases/20150529181638` folder. + +So if you use [codeigniter-composer-installer](https://github.com/kenjis/codeigniter-composer-installer)'s folder structure, your Apache configuratoin is like this: + +~~~ +DocumentRoot /var/www/your-codeigniter-app/current/public +~~~ + +If you use CodeIgniter as the default folder structure, your Apache configuratoin is like this: + +~~~ +DocumentRoot /var/www/your-codeigniter-app/current +~~~ + +### sudo + +Deployer will try to get write permission with the `sudo` command, so this command has to be running without prompt password and without tty. + +Here is an example of configuration: + +~~~ +Defaults:username !requiretty + +username ALL=(ALL) NOPASSWD: /usr/bin/setfacl +~~~ + +If you don't need `sudo` for your deployment, you can set in `deploy/deployer.php`. + +~~~php +set('writable_use_sudo', false); +~~~ + +## How to Deploy + +~~~ +$ cd /path/to/codeigniter/ +$ cd deploy/ +$ sh deploy.sh +~~~ + +## Related Projects for CodeIgniter 3.0 + +* [CodeIgniter Composer Installer](https://github.com/kenjis/codeigniter-composer-installer) +* [Cli for CodeIgniter 3.0](https://github.com/kenjis/codeigniter-cli) +* [CI PHPUnit Test](https://github.com/kenjis/ci-phpunit-test) +* [CodeIgniter Simple and Secure Twig](https://github.com/kenjis/codeigniter-ss-twig) +* [CodeIgniter Doctrine](https://github.com/kenjis/codeigniter-doctrine) diff --git a/adminfiles/css/style.css b/adminfiles/css/style.css index 830d5c2..84cd2bf 100644 --- a/adminfiles/css/style.css +++ b/adminfiles/css/style.css @@ -148,8 +148,8 @@ .progressSlim.blue .ui-progressbar-value, .label-info, .badge-info { - background: #578EBE !important; - border-color: #578EBE !important; + background: #0db0dd !important; + border-color: #0db0dd !important; color: #fff; } @@ -461,16 +461,16 @@ a.brand span { .nav-tabs.nav-stacked > li.active > a, .nav-tabs.nav-stacked > li > ul > li.active > a { - background: #578EBE; - border-bottom: 1px solid #578EBE; + background: #0db0dd; + border-bottom: 1px solid #0db0dd; } .nav-tabs.nav-stacked > li.active > a:hover, .nav-tabs.nav-stacked > li > ul > li.active > a:hover { - background: #578EBE; + background: #0db0dd; color: #fff; border: none; - border-bottom: 1px solid #578EBE; + border-bottom: 1px solid #0db0dd; } .header-nav .nav li:hover{ background: #3A3A3A; @@ -587,14 +587,14 @@ border-bottom: 1px solid transparent; .dropdown-submenu:hover > a { color: #ffffff; text-decoration: none; - background: #578EBE; + background: #0db0dd; } .dropdown-menu .active > a, .dropdown-menu .active > a:hover { color: #ffffff; text-decoration: none; - background: #578EBE; + background: #0db0dd; } .dropdown-menu .disabled > a, @@ -824,7 +824,7 @@ ul.messages li .message { } .box { - border: 2px solid #578EBE; + border: 2px solid #0db0dd; background: #fff !important; margin-bottom: 28px; } @@ -834,7 +834,7 @@ ul.messages li .message { font-size: 16px; line-height: 16px; padding: 10px; - background: #578EBE; + background: #0db0dd; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; @@ -1656,8 +1656,8 @@ fieldset[disabled] .btn:active { } .btn-primary { - background-color: #578EBE; - border-color: #578EBE; + background-color: #0db0dd; + border-color: #0db0dd; } .btn-primary:hover, @@ -1676,8 +1676,8 @@ fieldset[disabled] .btn-primary:focus, .btn-primary.disabled:active, .btn-primary[disabled]:active, fieldset[disabled] .btn-primary:active { - background-color: #578EBE; - border-color: #578EBE; + background-color: #0db0dd; + border-color: #0db0dd; } .btn-warning { @@ -1799,7 +1799,7 @@ fieldset[disabled] .btn-link { .btn-link { font-weight: normal; - color: #578EBE; + color: #0db0dd; cursor: pointer; border-radius: 0; } diff --git a/application/.htaccess b/application/.htaccess index 6c63ed4..2bfa303 100644 --- a/application/.htaccess +++ b/application/.htaccess @@ -1,6 +1,13 @@ - - Require all denied - - - Deny from all - \ No newline at end of file +RewriteEngine on + + +AcceptPathInfo On + + +RewriteCond $1 !^(index\.php|resources|assets|robots\.txt) + +RewriteCond %{REQUEST_FILENAME} !-f + +RewriteCond %{REQUEST_FILENAME} !-d + +RewriteRule ^(.*)$ index.php?/$1 [L,QSA] \ No newline at end of file diff --git a/application/config/autoload.php b/application/config/autoload.php index 30b8a37..9c08784 100644 --- a/application/config/autoload.php +++ b/application/config/autoload.php @@ -39,6 +39,7 @@ | $autoload['packages'] = array(APPPATH.'third_party', '/usr/local/shared'); | */ +$autoload['libraries'] = array('facebook'); $autoload['packages'] = array(); /* diff --git a/application/config/config.php b/application/config/config.php index 3299e3c..dc0dbf7 100644 --- a/application/config/config.php +++ b/application/config/config.php @@ -23,7 +23,7 @@ | a PHP script and you can easily do that on your own. | */ -$config['base_url'] = 'http://127.0.0.1/pfeprojet/'; +$config['base_url'] = 'http://localhost/codeignitershop/'; /* |-------------------------------------------------------------------------- @@ -136,7 +136,7 @@ | Note: This will NOT disable or override the CodeIgniter-specific | autoloading (application/config/autoload.php) */ -$config['composer_autoload'] = FALSE; +$config['composer_autoload'] = TRUE; /* |-------------------------------------------------------------------------- @@ -379,7 +379,7 @@ */ $config['sess_driver'] = 'database'; $config['sess_cookie_name'] = 'ci_session'; -$config['sess_expiration'] = 7200; +$config['sess_expiration'] = 7500; $config['sess_save_path'] = 'ci_sessions'; $config['sess_match_ip'] = FALSE; $config['sess_time_to_update'] = 300; diff --git a/application/config/database.php b/application/config/database.php index 708ec65..c08d07e 100644 --- a/application/config/database.php +++ b/application/config/database.php @@ -78,7 +78,7 @@ 'hostname' => 'localhost', 'username' => 'root', 'password' => '', - 'database' => 'djerbashop', + 'database' => 'technipackpfe', 'dbdriver' => 'mysqli', 'dbprefix' => '', 'pconnect' => FALSE, diff --git a/application/config/email.php b/application/config/email.php new file mode 100644 index 0000000..48238ae --- /dev/null +++ b/application/config/email.php @@ -0,0 +1,35 @@ + 5 mins). + * Long time bans could be abused by attackers to deny legitimate users access, it is designed to SLOW DOWN brute force attackers, not outright ban them. + * + * Example: Time in seconds, 0 = no time ban, 10 = 10 seconds, 60*3 = 3 minutes. + */ + $config['security']['login_attempt_time_ban'] = 10; + + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + // Google reCAPTCHA SETTINGS + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * flexi auth Google reCAPTCHA Settings. + * Google reCAPTCHA can be used to help slow down brute force login attempts, requiring the user to complete the CAPTCHA before their login details will be submitted. + * + * Note: Only change the value after the '=' sign, and not the $config array names. + * Example: Change $config['security']['example'] = 'example_value_1' to $config['security']['example'] = 'example_value_2' + */ + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * reCAPTCHA Keys + * Set your unique reCAPTCHA api keys. + * !IMPORTANT: Obtain YOUR OWN reCAPTCHA keys from http://www.google.com/recaptcha. + */ + $config['security']['recaptcha_public_key'] = 'ENTER_RECAPTCHA_PUBLIC_KEY_HERE'; + $config['security']['recaptcha_private_key'] = 'ENTER_RECAPTCHA_PRIVATE_KEY_HERE'; + + /** + * Set the theme of the reCAPTCHA. For custom theming, see https://developers.google.com/recaptcha/docs/customization + * Predefined themes: 'red', 'white', 'blackglass', 'clean'. Set 'custom' for custom themes. + */ + $config['security']['recaptcha_theme'] = 'white'; + + /** + * Set the language of the reCAPTCHA. + * Supported languages: English 'en', Dutch 'nl', French 'fr', German 'de', Portuguese 'pt', Russian 'ru', Spanish 'es', Turkish 'tr'. + */ + $config['security']['recaptcha_language'] = 'en'; + + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + // GENERAL CONFIGURATION SETTINGS + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * General flexi auth Settings + * Many of flexi auths automatic functions are customisable and can even be turned on and off to suit different websites. + * + * Note: Only change the value after the '=' sign, and not the $config array names. + * Example: Change $config['settings']['example'] = TRUE to $config['settings']['example'] = FALSE + */ + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * Set whether an incremented number is added to the end of an unavailable username. + * Example: If username 'flexi' is already in use, the next user to use 'flexi' as their username will be automatically updated to 'flexi1'. + * @param: bool + * + * Note: This only applies if the username is not set as the primary identity column ($config['database']['settings']['primary_identity_col']) + */ + $config['settings']['auto_increment_username'] = FALSE; + + /** + * Set whether accounts are suspended by default on registration / inserting user. + * This option allows admins to verify account details before enabling users. + * @param: bool + */ + $config['settings']['suspend_new_accounts'] = FALSE; + + /** + * Set a time limit to grant users instant login access, once expired, they are locked out until they activate their account via an activation email sent to them. + * @param: int + * + * Example: Time in minutes, 0 = unlimited, 60*24 = 24 hours, 1440 = 24 hours + */ + $config['settings']['account_activation_time_limit'] = 0; + + /** + * Set the id of the default group that new users will be added to unless otherwise specified. + * @param: int + */ + $config['settings']['default_group_id'] = 1; + + /** + * Set whether user privileges should be determined by individual privileges assigned per user, or via privileges assigned to a users user group. + * @param array + * + * Options: array('user','group'), array('user'), array('group') + */ + $config['settings']['privilege_sources'] = array('user','group'); + + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + // EMAIL CONFIGURATION SETTINGS + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * flexi auth Email Settings + * Some of the functions in flexi auth need to send emails to the user (i.e. 'Account Activation', 'Forgot Password' etc). + * If required, the title, reply address, email type and the content of these emails can be configured to suit different website needs. + * + * Note: Only change the value after the '=' sign, and not the $config array names. + * Example: Change $config['email']['example'] = 'example_value_1' to $config['email']['example'] = 'example_value_2' + */ + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + // Site title shown as 'from' header on emails. + $config['email']['site_title'] = "flexi auth"; + + // Reply email shown as 'from' header on emails. + $config['email']['reply_email'] = "info@website.com"; + + /** + * Type of email to send, options: 'html', 'text'. + * Note: If using 'text', the default code within the flexi auth templates use HTML which will be emailed as plain text. + */ + $config['email']['email_type'] = 'html'; + + /** + * Directory where email templates are stored. + * Default: 'includes/email/' + */ + $config['email']['email_template_directory'] = 'includes/email/'; + + /** + * 'Activate Account' email template. + * Default: 'activate_account.tpl.php' + */ + $config['email']['email_template_activate'] = 'activate_account.tpl.php'; + + /** + * 'Forgot Password' email template. + * Default: 'forgot_password.tpl.php' + */ + $config['email']['email_template_forgot_password'] = 'forgot_password.tpl.php'; + + /** + * 'Forgot Password Complete' email template. + * Default: 'new_password.tpl.php' + */ + $config['email']['email_template_forgot_password_complete'] = 'new_password.tpl.php'; + + /** + * 'Update Email' email template. + * Default: 'update_email_address.tpl.php' + */ + $config['email']['email_template_update_email'] = 'update_email_address.tpl.php'; + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + // MESSAGE SETTINGS + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * Message Delimiter Settings + * Define status and error message delimiters to style auth messages. + * @param: string + * + * Example: ['status_prefix'] = '

', ['status_suffix'] = '

' + */ + + // Message Start Delimiter + $config['messages']['delimiters']['status_prefix'] = '

'; + + // Message End Delimiter + $config['messages']['delimiters']['status_suffix'] = '

'; + + // Error Start Delimiter + $config['messages']['delimiters']['error_prefix'] = '

'; + + // Error End Delimiter + $config['messages']['delimiters']['error_suffix'] = '

'; + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * Message Visibility + * Define which status and error messages are returned as public or admin messages, or which messages are not returned at all. + * Public messages are intended to be displayed to public and admin users, whilst admin messages are intended for admin users only. + * + * Example: + * Public and Admin message = $config['messages']['target_user']['account_creation_successful'] = 'public'; + * Admin Only message = $config['messages']['target_user']['account_creation_successful'] = 'admin'; + * Do NOT set public or admin message = $config['messages']['target_user']['account_creation_successful'] = FALSE; + */ + + // Account Creation + $config['messages']['target_user']['account_creation_successful'] = 'public'; + $config['messages']['target_user']['account_creation_unsuccessful'] = 'public'; + $config['messages']['target_user']['account_creation_duplicate_email'] = 'public'; + $config['messages']['target_user']['account_creation_duplicate_username'] = 'public'; + $config['messages']['target_user']['account_creation_duplicate_identity'] = 'public'; + $config['messages']['target_user']['account_creation_insufficient_data'] = 'public'; + + // Password + $config['messages']['target_user']['password_invalid'] = 'public'; + $config['messages']['target_user']['password_change_successful'] = 'public'; + $config['messages']['target_user']['password_change_unsuccessful'] = 'public'; + $config['messages']['target_user']['password_token_invalid'] = 'public'; + $config['messages']['target_user']['email_new_password_successful'] = 'public'; + $config['messages']['target_user']['email_forgot_password_successful'] = 'public'; + $config['messages']['target_user']['email_forgot_password_unsuccessful'] = 'public'; + + // Activation + $config['messages']['target_user']['activate_successful'] = 'public'; + $config['messages']['target_user']['activate_unsuccessful'] = 'public'; + $config['messages']['target_user']['deactivate_successful'] = 'public'; + $config['messages']['target_user']['deactivate_unsuccessful'] = 'public'; + $config['messages']['target_user']['activation_email_successful'] = 'public'; + $config['messages']['target_user']['activation_email_unsuccessful'] = 'public'; + $config['messages']['target_user']['account_requires_activation'] = 'public'; + $config['messages']['target_user']['account_already_activated'] = 'public'; + $config['messages']['target_user']['email_activation_email_successful'] = 'public'; + $config['messages']['target_user']['email_activation_email_unsuccessful'] = 'public'; + + // Login / Logout + $config['messages']['target_user']['login_successful'] = 'public'; + $config['messages']['target_user']['login_unsuccessful'] = 'public'; + $config['messages']['target_user']['logout_successful'] = 'public'; + $config['messages']['target_user']['login_details_invalid'] = 'public'; + $config['messages']['target_user']['captcha_answer_invalid'] = 'public'; + $config['messages']['target_user']['login_attempts_exceeded'] = 'public'; + $config['messages']['target_user']['login_session_expired'] = 'public'; + $config['messages']['target_user']['account_suspended'] = 'public'; + + // Account Changes + $config['messages']['target_user']['update_successful'] = 'public'; + $config['messages']['target_user']['update_unsuccessful'] = 'public'; + $config['messages']['target_user']['delete_successful'] = 'public'; + $config['messages']['target_user']['delete_unsuccessful'] = 'public'; + + // Form Validation + $config['messages']['target_user']['form_validation_duplicate_identity'] = 'public'; + $config['messages']['target_user']['form_validation_duplicate_email'] = 'public'; + $config['messages']['target_user']['form_validation_duplicate_username'] = 'public'; + +/* End of file flexi_auth.php */ +/* Location: ./system/application/config/flexi_auth.php */ \ No newline at end of file diff --git a/application/config/geolocation.php b/application/config/geolocation.php new file mode 100644 index 0000000..a46c24e --- /dev/null +++ b/application/config/geolocation.php @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/application/config/hybridauthlib.php b/application/config/hybridauthlib.php new file mode 100644 index 0000000..3d6f49d --- /dev/null +++ b/application/config/hybridauthlib.php @@ -0,0 +1,77 @@ + '/hauth/endpoint', + + "providers" => array ( + // openid providers + "OpenID" => array ( + "enabled" => true + ), + + "Yahoo" => array ( + "enabled" => true, + "keys" => array ( "id" => "", "secret" => "" ), + ), + + "AOL" => array ( + "enabled" => true + ), + + "Google" => array ( + "enabled" => true, + "keys" => array ( "id" => "", "secret" => "" ), + ), + + "Facebook" => array ( + "enabled" => true, + "keys" => array ( "id" => "440396449754533", "secret" => "744ea8bdf72e7809d9dd78dfc91a5264" ), + ), + + "Twitter" => array ( + "enabled" => true, + "keys" => array ( "key" => "", "secret" => "" ) + ), + + // windows live + "Live" => array ( + "enabled" => true, + "keys" => array ( "id" => "", "secret" => "" ) + ), + + "MySpace" => array ( + "enabled" => true, + "keys" => array ( "key" => "", "secret" => "" ) + ), + + "LinkedIn" => array ( + "enabled" => true, + "keys" => array ( "key" => "", "secret" => "" ) + ), + + "Foursquare" => array ( + "enabled" => true, + "keys" => array ( "id" => "", "secret" => "" ) + ), + ), + + // if you want to enable logging, set 'debug_mode' to true then provide a writable file by the web server on "debug_file" + "debug_mode" => (ENVIRONMENT == 'development'), + + "debug_file" => APPPATH.'/logs/hybridauth.log', + ); + + +/* End of file hybridauthlib.php */ +/* Location: ./application/config/hybridauthlib.php */ \ No newline at end of file diff --git a/application/config/ion_auth.php b/application/config/ion_auth.php new file mode 100644 index 0000000..8b3ac09 --- /dev/null +++ b/application/config/ion_auth.php @@ -0,0 +1,188 @@ +ion_auth->is_max_login_attempts_exceeded(). + | The controller should check this function and act + | appropriately. If this variable set to 0, there is no maximum. + */ +$config['site_title'] = "Réinitialiser votre mot de passe"; // Site Title, example.com +$config['admin_email'] = "yourmail@gmail.com"; // Admin Email, admin@example.com +$config['default_group'] = 'admin'; // Default group, use name +$config['admin_group'] = 'admin'; // Default administrators group, use name +$config['identity'] = 'email'; // You can use any unique column in your table as identity column. The values in this column, alongside password, will be used for login purposes +$config['min_password_length'] = 8; // Minimum Required Length of Password +$config['max_password_length'] = 20; // Maximum Allowed Length of Password +$config['email_activation'] = TRUE; // Email Activation for registration +$config['manual_activation'] = TRUE; // Manual Activation for registration +$config['remember_users'] = TRUE; // Allow users to be remembered and enable auto-login +$config['user_expire'] = 900; // How long to remember the user (seconds). Set to zero for no expiration +$config['user_extend_on_login'] = FALSE; // Extend the users cookies every time they auto-login +$config['track_login_attempts'] = TRUE; // Track the number of failed login attempts for each user or ip. +$config['track_login_ip_address'] = TRUE; // Track login attempts by IP Address, if FALSE will track based on identity. (Default: TRUE) +$config['maximum_login_attempts'] = 3; // The maximum number of failed login attempts. +$config['lockout_time'] = 600; /* The number of seconds to lockout an account due to exceeded attempts + You should not use a value below 60 (1 minute) */ +$config['forgot_password_expiration'] = 0; // The number of milliseconds after which a forgot password request will expire. If set to 0, forgot password requests will not expire. +$config['recheck_timer'] = 0; /* The number of seconds after which the session is checked again against database to see if the user still exists and is active. + Leave 0 if you don't want session recheck. if you really think you need to recheck the session against database, we would + recommend a higher value, as this would affect performance */ + + +/* + | ------------------------------------------------------------------------- + | Cookie options. + | ------------------------------------------------------------------------- + | remember_cookie_name Default: remember_code + | identity_cookie_name Default: identity + */ +$config['remember_cookie_name'] = 'remember_code'; +$config['identity_cookie_name'] = 'identity'; + +/* + | ------------------------------------------------------------------------- + | Email options. + | ------------------------------------------------------------------------- + | email_config: + | 'file' = Use the default CI config or use from a config file + | array = Manually set your email config settings + */ +$config['use_ci_email'] = TRUE; +$config['email_config'] ='file'; + +/* + | ------------------------------------------------------------------------- + | Email templates. + | ------------------------------------------------------------------------- + | Folder where email templates are stored. + | Default: auth/ + */ +$config['email_templates'] = 'auth/email/'; + +/* + | ------------------------------------------------------------------------- + | Activate Account Email Template + | ------------------------------------------------------------------------- + | Default: activate.tpl.php + */ +$config['email_activate'] = 'activate.tpl.php'; + +/* + | ------------------------------------------------------------------------- + | Forgot Password Email Template + | ------------------------------------------------------------------------- + | Default: forgot_password.tpl.php + */ +$config['email_forgot_password'] = 'forgot_password.tpl.php'; + +/* + | ------------------------------------------------------------------------- + | Forgot Password Complete Email Template + | ------------------------------------------------------------------------- + | Default: new_password.tpl.php + */ +$config['email_forgot_password_complete'] = 'new_password.tpl.php'; + +/* + | ------------------------------------------------------------------------- + | Salt options + | ------------------------------------------------------------------------- + | salt_length Default: 22 + | + | store_salt: Should the salt be stored in the database? + | This will change your password encryption algorithm, + | default password, 'password', changes to + | fbaa5e216d163a02ae630ab1a43372635dd374c0 with default salt. + */ +$config['salt_length'] = 22; +$config['store_salt'] = FALSE; + +/* + | ------------------------------------------------------------------------- + | Message Delimiters. + | ------------------------------------------------------------------------- + */ +$config['delimiters_source'] = 'config'; // "config" = use the settings defined here, "form_validation" = use the settings defined in CI's form validation library +$config['message_start_delimiter'] = '

'; // Message start delimiter +$config['message_end_delimiter'] = '

'; // Message end delimiter +$config['error_start_delimiter'] = '

'; // Error message start delimiter +$config['error_end_delimiter'] = '

'; // Error message end delimiter + +/* End of file ion_auth.php */ +/* Location: ./application/config/ion_auth.php */ diff --git a/application/config/rest.php b/application/config/rest.php new file mode 100644 index 0000000..71704cd --- /dev/null +++ b/application/config/rest.php @@ -0,0 +1,489 @@ +function($username, $password) +| In other cases override the function _perform_library_auth in your controller +| +| For digest authentication the library function should return already a stored +| md5(username:restrealm:password) for that username +| +| e.g: md5('admin:REST API:1234') = '1e957ebc35631ab22d5bd6526bd14ea2' +| +*/ +$config['auth_library_class'] = ''; +$config['auth_library_function'] = ''; + +/* +|-------------------------------------------------------------------------- +| Override auth types for specific class/method +|-------------------------------------------------------------------------- +| +| Set specific authentication types for methods within a class (controller) +| +| Set as many config entries as needed. Any methods not set will use the default 'rest_auth' config value. +| +| e.g: +| +| $config['auth_override_class_method']['deals']['view'] = 'none'; +| $config['auth_override_class_method']['deals']['insert'] = 'digest'; +| $config['auth_override_class_method']['accounts']['user'] = 'basic'; +| $config['auth_override_class_method']['dashboard']['*'] = 'none|digest|basic'; +| +| Here 'deals', 'accounts' and 'dashboard' are controller names, 'view', 'insert' and 'user' are methods within. An asterisk may also be used to specify an authentication method for an entire classes methods. Ex: $config['auth_override_class_method']['dashboard']['*'] = 'basic'; (NOTE: leave off the '_get' or '_post' from the end of the method name) +| Acceptable values are; 'none', 'digest' and 'basic'. +| +*/ +// $config['auth_override_class_method']['deals']['view'] = 'none'; +// $config['auth_override_class_method']['deals']['insert'] = 'digest'; +// $config['auth_override_class_method']['accounts']['user'] = 'basic'; +// $config['auth_override_class_method']['dashboard']['*'] = 'basic'; + + +// ---Uncomment list line for the wildard unit test +// $config['auth_override_class_method']['wildcard_test_cases']['*'] = 'basic'; + +/* +|-------------------------------------------------------------------------- +| Override auth types for specfic 'class/method/HTTP method' +|-------------------------------------------------------------------------- +| +| example: +| +| $config['auth_override_class_method_http']['deals']['view']['get'] = 'none'; +| $config['auth_override_class_method_http']['deals']['insert']['post'] = 'none'; +| $config['auth_override_class_method_http']['deals']['*']['options'] = 'none'; +*/ + +// ---Uncomment list line for the wildard unit test +// $config['auth_override_class_method_http']['wildcard_test_cases']['*']['options'] = 'basic'; + +/* +|-------------------------------------------------------------------------- +| REST Login Usernames +|-------------------------------------------------------------------------- +| +| Array of usernames and passwords for login, if ldap is configured this is ignored +| +*/ +$config['rest_valid_logins'] = ['admin' => '1234']; + +/* +|-------------------------------------------------------------------------- +| Global IP Whitelisting +|-------------------------------------------------------------------------- +| +| Limit connections to your REST server to whitelisted IP addresses +| +| Usage: +| 1. Set to TRUE and select an auth option for extreme security (client's IP +| address must be in whitelist and they must also log in) +| 2. Set to TRUE with auth set to FALSE to allow whitelisted IPs access with no login +| 3. Set to FALSE but set 'auth_override_class_method' to 'whitelist' to +| restrict certain methods to IPs in your whitelist +| +*/ +$config['rest_ip_whitelist_enabled'] = FALSE; + +/* +|-------------------------------------------------------------------------- +| REST IP Whitelist +|-------------------------------------------------------------------------- +| +| Limit connections to your REST server with a comma separated +| list of IP addresses +| +| e.g: '123.456.789.0, 987.654.32.1' +| +| 127.0.0.1 and 0.0.0.0 are allowed by default +| +*/ +$config['rest_ip_whitelist'] = ''; + +/* +|-------------------------------------------------------------------------- +| Global IP Blacklisting +|-------------------------------------------------------------------------- +| +| Prevent connections to the REST server from blacklisted IP addresses +| +| Usage: +| 1. Set to TRUE and add any IP address to 'rest_ip_blacklist' +| +*/ +$config['rest_ip_blacklist_enabled'] = FALSE; + +/* +|-------------------------------------------------------------------------- +| REST IP Blacklist +|-------------------------------------------------------------------------- +| +| Prevent connections from the following IP addresses +| +| e.g: '123.456.789.0, 987.654.32.1' +| +*/ +$config['rest_ip_blacklist'] = ''; + +/* +|-------------------------------------------------------------------------- +| REST Database Group +|-------------------------------------------------------------------------- +| +| Connect to a database group for keys, logging, etc. It will only connect +| if you have any of these features enabled +| +*/ +$config['rest_database_group'] = 'default'; + +/* +|-------------------------------------------------------------------------- +| REST API Keys Table Name +|-------------------------------------------------------------------------- +| +| The table name in your database that stores API keys +| +*/ +$config['rest_keys_table'] = 'keys'; + +/* +|-------------------------------------------------------------------------- +| REST Enable Keys +|-------------------------------------------------------------------------- +| +| When set to TRUE, the REST API will look for a column name called 'key'. +| If no key is provided, the request will result in an error. To override the +| column name see 'rest_key_column' +| +| Default table schema: +| CREATE TABLE `keys` ( +| `id` INT(11) NOT NULL AUTO_INCREMENT, +| `key` VARCHAR(40) NOT NULL, +| `level` INT(2) NOT NULL, +| `ignore_limits` TINYINT(1) NOT NULL DEFAULT '0', +| `is_private_key` TINYINT(1) NOT NULL DEFAULT '0', +| `ip_addresses` TEXT NULL DEFAULT NULL, +| `date_created` INT(11) NOT NULL, +| PRIMARY KEY (`id`) +| ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +| +*/ +$config['rest_enable_keys'] = FALSE; + +/* +|-------------------------------------------------------------------------- +| REST Table Key Column Name +|-------------------------------------------------------------------------- +| +| If not using the default table schema in 'rest_enable_keys', specify the +| column name to match e.g. my_key +| +*/ +$config['rest_key_column'] = 'key'; + +/* +|-------------------------------------------------------------------------- +| REST API Limits method +|-------------------------------------------------------------------------- +| +| Specify the method used to limit the API calls +| +| Available methods are : +| $config['rest_limits_method'] = 'API_KEY'; // Put a limit per api key +| $config['rest_limits_method'] = 'METHOD_NAME'; // Put a limit on method calls +| $config['rest_limits_method'] = 'ROUTED_URL'; // Put a limit on the routed URL +| +*/ +$config['rest_limits_method'] = 'ROUTED_URL'; + +/* +|-------------------------------------------------------------------------- +| REST Key Length +|-------------------------------------------------------------------------- +| +| Length of the created keys. Check your default database schema on the +| maximum length allowed +| +| Note: The maximum length is 40 +| +*/ +$config['rest_key_length'] = 40; + +/* +|-------------------------------------------------------------------------- +| REST API Key Variable +|-------------------------------------------------------------------------- +| +| Custom header to specify the API key + +| Note: Custom headers with the X- prefix are deprecated as of +| 2012/06/12. See RFC 6648 specification for more details +| +*/ +$config['rest_key_name'] = 'X-API-KEY'; + +/* +|-------------------------------------------------------------------------- +| REST Enable Logging +|-------------------------------------------------------------------------- +| +| When set to TRUE, the REST API will log actions based on the column names 'key', 'date', +| 'time' and 'ip_address'. This is a general rule that can be overridden in the +| $this->method array for each controller +| +| Default table schema: +| CREATE TABLE `logs` ( +| `id` INT(11) NOT NULL AUTO_INCREMENT, +| `uri` VARCHAR(255) NOT NULL, +| `method` VARCHAR(6) NOT NULL, +| `params` TEXT DEFAULT NULL, +| `api_key` VARCHAR(40) NOT NULL, +| `ip_address` VARCHAR(45) NOT NULL, +| `time` INT(11) NOT NULL, +| `rtime` FLOAT DEFAULT NULL, +| `authorized` VARCHAR(1) NOT NULL, +| `response_code` smallint(3) DEFAULT '0', +| PRIMARY KEY (`id`) +| ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +| +*/ +$config['rest_enable_logging'] = FALSE; + +/* +|-------------------------------------------------------------------------- +| REST API Logs Table Name +|-------------------------------------------------------------------------- +| +| If not using the default table schema in 'rest_enable_logging', specify the +| table name to match e.g. my_logs +| +*/ +$config['rest_logs_table'] = 'logs'; + +/* +|-------------------------------------------------------------------------- +| REST Method Access Control +|-------------------------------------------------------------------------- +| When set to TRUE, the REST API will check the access table to see if +| the API key can access that controller. 'rest_enable_keys' must be enabled +| to use this +| +| Default table schema: +| CREATE TABLE `access` ( +| `id` INT(11) unsigned NOT NULL AUTO_INCREMENT, +| `key` VARCHAR(40) NOT NULL DEFAULT '', +| `controller` VARCHAR(50) NOT NULL DEFAULT '', +| `date_created` DATETIME DEFAULT NULL, +| `date_modified` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, +| PRIMARY KEY (`id`) +| ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +| +*/ +$config['rest_enable_access'] = FALSE; + +/* +|-------------------------------------------------------------------------- +| REST API Access Table Name +|-------------------------------------------------------------------------- +| +| If not using the default table schema in 'rest_enable_access', specify the +| table name to match e.g. my_access +| +*/ +$config['rest_access_table'] = 'access'; + +/* +|-------------------------------------------------------------------------- +| REST API Param Log Format +|-------------------------------------------------------------------------- +| +| When set to TRUE, the REST API log parameters will be stored in the database as JSON +| Set to FALSE to log as serialized PHP +| +*/ +$config['rest_logs_json_params'] = FALSE; + +/* +|-------------------------------------------------------------------------- +| REST Enable Limits +|-------------------------------------------------------------------------- +| +| When set to TRUE, the REST API will count the number of uses of each method +| by an API key each hour. This is a general rule that can be overridden in the +| $this->method array in each controller +| +| Default table schema: +| CREATE TABLE `limits` ( +| `id` INT(11) NOT NULL AUTO_INCREMENT, +| `uri` VARCHAR(255) NOT NULL, +| `count` INT(10) NOT NULL, +| `hour_started` INT(11) NOT NULL, +| `api_key` VARCHAR(40) NOT NULL, +| PRIMARY KEY (`id`) +| ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +| +| To specify the limits within the controller's __construct() method, add per-method +| limits with: +| +| $this->method['METHOD_NAME']['limit'] = [NUM_REQUESTS_PER_HOUR]; +| +| See application/controllers/api/example.php for examples +*/ +$config['rest_enable_limits'] = FALSE; + +/* +|-------------------------------------------------------------------------- +| REST API Limits Table Name +|-------------------------------------------------------------------------- +| +| If not using the default table schema in 'rest_enable_limits', specify the +| table name to match e.g. my_limits +| +*/ +$config['rest_limits_table'] = 'limits'; + +/* +|-------------------------------------------------------------------------- +| REST Ignore HTTP Accept +|-------------------------------------------------------------------------- +| +| Set to TRUE to ignore the HTTP Accept and speed up each request a little. +| Only do this if you are using the $this->rest_format or /format/xml in URLs +| +*/ +$config['rest_ignore_http_accept'] = FALSE; + +/* +|-------------------------------------------------------------------------- +| REST AJAX Only +|-------------------------------------------------------------------------- +| +| Set to TRUE to allow AJAX requests only. Set to FALSE to accept HTTP requests +| +| Note: If set to TRUE and the request is not AJAX, a 505 response with the +| error message 'Only AJAX requests are accepted.' will be returned. +| +| Hint: This is good for production environments +| +*/ +$config['rest_ajax_only'] = FALSE; + +/* +|-------------------------------------------------------------------------- +| REST Language File +|-------------------------------------------------------------------------- +| +| Language file to load from the language directory +| +*/ +$config['rest_language'] = 'english'; diff --git a/application/config/routes.php b/application/config/routes.php index f432a17..0f740a3 100644 --- a/application/config/routes.php +++ b/application/config/routes.php @@ -49,6 +49,6 @@ | Examples: my-controller/index -> my_controller/index | my-controller/my-method -> my_controller/my_method */ -$route['default_controller'] = 'default_module'; +$route['default_controller'] = 'default_module';// baddalha homepage $route['404_override'] = 'default_module'; $route['translate_uri_dashes'] = FALSE; diff --git a/application/config/tank_auth.php b/application/config/tank_auth.php new file mode 100644 index 0000000..8bf640d --- /dev/null +++ b/application/config/tank_auth.php @@ -0,0 +1,145 @@ +head_data['css'] = [ + 'css/bootstrap/bootstrap.min.css', + 'css/bootstrap/bootstrap-theme.min.css', + 'css/lte/AdminLTE.min.css', + 'css/lte/style.css', + 'css/style.css', + ]; + $this->head_data['js'] = [ + 'js/jquery/jQuery-2.1.4.min.js', + 'js/bootstrap/bootstrap.min.js', + 'js/angular/angular.min.js', + 'js/angular/angular-postfix/angular-postfix.js', + 'js/angular/bootstrap-angular-ui/ui-bootstrap-0.13.1.min.js', + 'js/angular/bootstrap-angular-ui/ui-bootstrap-tpls-0.13.1.min.js', + 'js/angular/app.js' + ]; + } + + public function index($page = 'index') + { + $this->head_data['title'] = 'Главная страница'; + $this->content['body'] = 'Привет'; + $this->loadTemplates(); + } + + protected function loadTemplates() + { + $this->load->view('templates/header', $this->head_data); + $this->load->view('templates/body', $this->content); + $this->load->view('templates/footer'); + } +} diff --git a/application/helpers/facebook_helper.php b/application/helpers/facebook_helper.php new file mode 100644 index 0000000..62ce698 --- /dev/null +++ b/application/helpers/facebook_helper.php @@ -0,0 +1,40 @@ +config->item('facebook_app_id'); + } + + function facebook_picture($who = 'me') + { + $ci =& get_instance(); + + return $ci->facebook->append_token($ci->config->item('facebook_api_url').$who.'/picture'); + } + + function facebook_opengraph_meta($opengraph) + { + $ci =& get_instance(); + + $return = ''; + $return .= "\n"; + $return .= ''; + $return .= "\n"; + $return .= ''; + $return .= "\n"; + + foreach ( $opengraph as $key => $value ) + { + $return .= ''; + $return .= "\n"; + } + + return $return; + } \ No newline at end of file diff --git a/application/helpers/recaptcha_helper.php b/application/helpers/recaptcha_helper.php new file mode 100644 index 0000000..32c4f4d --- /dev/null +++ b/application/helpers/recaptcha_helper.php @@ -0,0 +1,277 @@ + $value ) + $req .= $key . '=' . urlencode( stripslashes($value) ) . '&'; + + // Cut the last '&' + $req=substr($req,0,strlen($req)-1); + return $req; +} + + + +/** + * Submits an HTTP POST to a reCAPTCHA server + * @param string $host + * @param string $path + * @param array $data + * @param int port + * @return array response + */ +function _recaptcha_http_post($host, $path, $data, $port = 80) { + + $req = _recaptcha_qsencode ($data); + + $http_request = "POST $path HTTP/1.0\r\n"; + $http_request .= "Host: $host\r\n"; + $http_request .= "Content-Type: application/x-www-form-urlencoded;\r\n"; + $http_request .= "Content-Length: " . strlen($req) . "\r\n"; + $http_request .= "User-Agent: reCAPTCHA/PHP\r\n"; + $http_request .= "\r\n"; + $http_request .= $req; + + $response = ''; + if( false == ( $fs = @fsockopen($host, $port, $errno, $errstr, 10) ) ) { + die ('Could not open socket'); + } + + fwrite($fs, $http_request); + + while ( !feof($fs) ) + $response .= fgets($fs, 1160); // One TCP-IP packet + fclose($fs); + $response = explode("\r\n\r\n", $response, 2); + + return $response; +} + + + +/** + * Gets the challenge HTML (javascript and non-javascript version). + * This is called from the browser, and the resulting reCAPTCHA HTML widget + * is embedded within the HTML form it was called from. + * @param string $pubkey A public key for reCAPTCHA + * @param string $error The error given by reCAPTCHA (optional, default is null) + * @param boolean $use_ssl Should the request be made over ssl? (optional, default is false) + + * @return string - The HTML to be embedded in the user's form. + */ +function recaptcha_get_html ($pubkey, $error = null, $use_ssl = false) +{ + if ($pubkey == null || $pubkey == '') { + die ("To use reCAPTCHA you must get an API key from https://www.google.com/recaptcha/admin/create"); + } + + if ($use_ssl) { + $server = RECAPTCHA_API_SECURE_SERVER; + } else { + $server = RECAPTCHA_API_SERVER; + } + + $errorpart = ""; + if ($error) { + $errorpart = "&error=" . $error; + } + return ' + + '; +} + + + + +/** + * A ReCaptchaResponse is returned from recaptcha_check_answer() + */ +class ReCaptchaResponse { + var $is_valid; + var $error; +} + + +/** + * Calls an HTTP POST function to verify if the user's guess was correct + * @param string $privkey + * @param string $remoteip + * @param string $challenge + * @param string $response + * @param array $extra_params an array of extra variables to post to the server + * @return ReCaptchaResponse + */ +function recaptcha_check_answer ($privkey, $remoteip, $challenge, $response, $extra_params = array()) +{ + if ($privkey == null || $privkey == '') { + die ("To use reCAPTCHA you must get an API key from https://www.google.com/recaptcha/admin/create"); + } + + if ($remoteip == null || $remoteip == '') { + die ("For security reasons, you must pass the remote ip to reCAPTCHA"); + } + + + + //discard spam submissions + if ($challenge == null || strlen($challenge) == 0 || $response == null || strlen($response) == 0) { + $recaptcha_response = new ReCaptchaResponse(); + $recaptcha_response->is_valid = false; + $recaptcha_response->error = 'incorrect-captcha-sol'; + return $recaptcha_response; + } + + $response = _recaptcha_http_post (RECAPTCHA_VERIFY_SERVER, "/recaptcha/api/verify", + array ( + 'privatekey' => $privkey, + 'remoteip' => $remoteip, + 'challenge' => $challenge, + 'response' => $response + ) + $extra_params + ); + + $answers = explode ("\n", $response [1]); + $recaptcha_response = new ReCaptchaResponse(); + + if (trim ($answers [0]) == 'true') { + $recaptcha_response->is_valid = true; + } + else { + $recaptcha_response->is_valid = false; + $recaptcha_response->error = $answers [1]; + } + return $recaptcha_response; + +} + +/** + * gets a URL where the user can sign up for reCAPTCHA. If your application + * has a configuration page where you enter a key, you should provide a link + * using this function. + * @param string $domain The domain where the page is hosted + * @param string $appname The name of your application + */ +function recaptcha_get_signup_url ($domain = null, $appname = null) { + return "https://www.google.com/recaptcha/admin/create?" . _recaptcha_qsencode (array ('domains' => $domain, 'app' => $appname)); +} + +function _recaptcha_aes_pad($val) { + $block_size = 16; + $numpad = $block_size - (strlen ($val) % $block_size); + return str_pad($val, strlen ($val) + $numpad, chr($numpad)); +} + +/* Mailhide related code */ + +function _recaptcha_aes_encrypt($val,$ky) { + if (! function_exists ("mcrypt_encrypt")) { + die ("To use reCAPTCHA Mailhide, you need to have the mcrypt php module installed."); + } + $mode=MCRYPT_MODE_CBC; + $enc=MCRYPT_RIJNDAEL_128; + $val=_recaptcha_aes_pad($val); + return mcrypt_encrypt($enc, $ky, $val, $mode, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"); +} + + +function _recaptcha_mailhide_urlbase64 ($x) { + return strtr(base64_encode ($x), '+/', '-_'); +} + +/* gets the reCAPTCHA Mailhide url for a given email, public key and private key */ +function recaptcha_mailhide_url($pubkey, $privkey, $email) { + if ($pubkey == '' || $pubkey == null || $privkey == "" || $privkey == null) { + die ("To use reCAPTCHA Mailhide, you have to sign up for a public and private key, " . + "you can do so at http://www.google.com/recaptcha/mailhide/apikey"); + } + + + $ky = pack('H*', $privkey); + $cryptmail = _recaptcha_aes_encrypt ($email, $ky); + + return "http://www.google.com/recaptcha/mailhide/d?k=" . $pubkey . "&c=" . _recaptcha_mailhide_urlbase64 ($cryptmail); +} + +/** + * gets the parts of the email to expose to the user. + * eg, given johndoe@example,com return ["john", "example.com"]. + * the email is then displayed as john...@example.com + */ +function _recaptcha_mailhide_email_parts ($email) { + $arr = preg_split("/@/", $email ); + + if (strlen ($arr[0]) <= 4) { + $arr[0] = substr ($arr[0], 0, 1); + } else if (strlen ($arr[0]) <= 6) { + $arr[0] = substr ($arr[0], 0, 3); + } else { + $arr[0] = substr ($arr[0], 0, 4); + } + return $arr; +} + +/** + * Gets html to display an email address given a public an private key. + * to get a key, go to: + * + * http://www.google.com/recaptcha/mailhide/apikey + */ +function recaptcha_mailhide_html($pubkey, $privkey, $email) { + $emailparts = _recaptcha_mailhide_email_parts ($email); + $url = recaptcha_mailhide_url ($pubkey, $privkey, $email); + + return htmlentities($emailparts[0]) . "...@" . htmlentities ($emailparts [1]); + +} + + +?> diff --git a/application/language/arabic/auth_lang.php b/application/language/arabic/auth_lang.php new file mode 100644 index 0000000..b290c92 --- /dev/null +++ b/application/language/arabic/auth_lang.php @@ -0,0 +1,163 @@ + \ No newline at end of file diff --git a/application/language/english/ion_auth_lang.php b/application/language/english/ion_auth_lang.php new file mode 100644 index 0000000..086c465 --- /dev/null +++ b/application/language/english/ion_auth_lang.php @@ -0,0 +1,82 @@ + + * @link http://www.yorickpeterse.com/ + * + */ + +// Account Creation +$lang['account_creation_successful'] = 'Ahoy, Welcome Aboard Landlubber!'; +$lang['account_creation_unsuccessful'] = 'Avast, Unable to Commandeer Ship'; +$lang['account_creation_duplicate_email'] = 'Letter in the Bottle Already Used or Invalid'; +$lang['account_creation_duplicate_identity'] = 'Pirate Name Already Used or Invalid'; + +// TODO Please Translate +$lang['account_creation_missing_default_group'] = 'Default group is not set'; +$lang['account_creation_invalid_default_group'] = 'Invalid default group name set'; + + +// Password +$lang['password_change_successful'] = 'Secret Code Successfully Changed'; +$lang['password_change_unsuccessful'] = 'Unable to Change Secret Code'; +$lang['forgot_password_successful'] = 'Secret Code Reset Letter Sent'; +$lang['forgot_password_unsuccessful'] = 'Unable to Reset Secret Code'; + +// Activation +$lang['activate_successful'] = 'Ahoy, Your Ship Be Ready For Sailing The Seven Seas'; +$lang['activate_unsuccessful'] = 'Avast, Furner be having trouble!'; +$lang['deactivate_successful'] = 'Furner be burned down by the Navy'; +$lang['deactivate_unsuccessful'] = 'Shiver me timbers! Account not Deactivated'; +$lang['activation_email_successful'] = 'Letter in the Bottle Sent'; +$lang['activation_email_unsuccessful'] = 'Unable to Send Letter in the Bottle'; +$lang['deactivate_current_user_unsuccessful']= 'You cannot De-Activate your self.'; + +// Login / Logout +$lang['login_successful'] = 'Yarr, welcome aboard!'; +$lang['login_unsuccessful'] = 'In-Correct Secret Code'; +$lang['login_unsuccessful_not_active'] = 'Account is inactive'; +$lang['login_timeout'] = 'Temporarily Locked Out. Try again later.'; +$lang['logout_successful'] = 'Be Seeying ya Matey'; + +// Account Changes +$lang['update_successful'] = 'Ship Information Successfully Updated'; +$lang['update_unsuccessful'] = 'Unable to Update Ship Information'; +$lang['delete_successful'] = 'Pirate Sent to Davy Jones\' Locker'; +$lang['delete_unsuccessful'] = 'Avast, The Pirate be Still Alive'; + +// Groups +$lang['group_creation_successful'] = 'Group created Successfully'; +$lang['group_already_exists'] = 'Group name already taken'; +$lang['group_update_successful'] = 'Group details updated'; +$lang['group_delete_successful'] = 'Group deleted'; +$lang['group_delete_unsuccessful'] = 'Unable to delete group'; +$lang['group_delete_notallowed'] = 'Can\'t delete the administrators\' group'; +$lang['group_name_required'] = 'Group name is a required field'; +$lang['group_name_admin_not_alter'] = 'Admin group name can not be changed'; + +// Activation Email +$lang['email_activation_subject'] = 'Account Activation'; +$lang['email_activate_heading'] = 'Activate account for %s'; +$lang['email_activate_subheading'] = 'Please click this link to %s.'; +$lang['email_activate_link'] = 'Activate Your Account'; +// Forgot Password Email +$lang['email_forgotten_password_subject'] = 'Forgotten Password Verification'; +$lang['email_forgot_password_heading'] = 'Reset Password for %s'; +$lang['email_forgot_password_subheading'] = 'Please click this link to %s.'; +$lang['email_forgot_password_link'] = 'Reset Your Password'; +// New Password Email +$lang['email_new_password_subject'] = 'New Password'; +$lang['email_new_password_heading'] = 'New Password for %s'; +$lang['email_new_password_subheading'] = 'Your password has been reset to: %s'; diff --git a/application/language/polish/auth_lang.php b/application/language/polish/auth_lang.php new file mode 100644 index 0000000..ece6c38 --- /dev/null +++ b/application/language/polish/auth_lang.php @@ -0,0 +1,162 @@ +7, 'salt_prefix'=>'$2y$')) { + + if(CRYPT_BLOWFISH != 1) { + throw new Exception("bcrypt not supported in this installation. See http://php.net/crypt"); + } + + $this->rounds = $params['rounds']; + $this->salt_prefix = $params['salt_prefix']; + } + + public function hash($input) { + $hash = crypt($input, $this->getSalt()); + + if(strlen($hash) > 13) { + return $hash; + } + + return false; + } + + /** + * @param $input + * @param $existingHash + * @return bool + */ + public function verify($input, $existingHash) { + $hash = crypt($input, $existingHash); + return $this->hashEquals($existingHash, $hash); + } + + /** + * Polyfill for hash_equals() + * Code mainly taken from hash_equals() compat function of CodeIgniter 3 + * + * @param string $known_string + * @param string $user_string + * @return bool + */ + private function hashEquals($known_string, $user_string) + { + // For CI3 or PHP >= 5.6 + if (function_exists('hash_equals')) + { + return hash_equals($known_string, $user_string); + } + + // For CI2 with PHP < 5.6 + // Code from CI3 https://github.com/bcit-ci/CodeIgniter/blob/develop/system/core/compat/hash.php + if ( ! is_string($known_string)) + { + trigger_error('hash_equals(): Expected known_string to be a string, '.strtolower(gettype($known_string)).' given', E_USER_WARNING); + return FALSE; + } + elseif ( ! is_string($user_string)) + { + trigger_error('hash_equals(): Expected user_string to be a string, '.strtolower(gettype($user_string)).' given', E_USER_WARNING); + return FALSE; + } + elseif (($length = strlen($known_string)) !== strlen($user_string)) + { + return FALSE; + } + + $diff = 0; + for ($i = 0; $i < $length; $i++) + { + $diff |= ord($known_string[$i]) ^ ord($user_string[$i]); + } + + return ($diff === 0); + } + + private function getSalt() { + $salt = sprintf($this->salt_prefix.'%02d$', $this->rounds); + + $bytes = $this->getRandomBytes(16); + + $salt .= $this->encodeBytes($bytes); + + return $salt; + } + + private $randomState; + + + /** + * @param $count + * @return string + */ + private function getRandomBytes($count) { + $bytes = ''; + + if(function_exists('openssl_random_pseudo_bytes') && + (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN')) { // OpenSSL slow on Win + $bytes = openssl_random_pseudo_bytes($count); + } + + if($bytes === '' && @is_readable('/dev/urandom') && + ($hRand = @fopen('/dev/urandom', 'rb')) !== FALSE) { + $bytes = fread($hRand, $count); + fclose($hRand); + } + + if(strlen($bytes) < $count) { + $bytes = ''; + + if($this->randomState === null) { + $this->randomState = microtime(); + if(function_exists('getmypid')) { + $this->randomState .= getmypid(); + } + } + + for($i = 0; $i < $count; $i += 16) { + $this->randomState = md5(microtime() . $this->randomState); + + if (PHP_VERSION >= '5') { + $bytes .= md5($this->randomState, true); + } else { + $bytes .= pack('H*', md5($this->randomState)); + } + } + + $bytes = substr($bytes, 0, $count); + } + + return $bytes; + } + + /** + * @param $input + * @return string + */ + private function encodeBytes($input) { + // The following is code from the PHP Password Hashing Framework + $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + + $output = ''; + $i = 0; + do { + $c1 = ord($input[$i++]); + $output .= $itoa64[$c1 >> 2]; + $c1 = ($c1 & 0x03) << 4; + if ($i >= 16) { + $output .= $itoa64[$c1]; + break; + } + + $c2 = ord($input[$i++]); + $c1 |= $c2 >> 4; + $output .= $itoa64[$c1]; + $c1 = ($c2 & 0x0f) << 2; + + $c2 = ord($input[$i++]); + $c1 |= $c2 >> 6; + $output .= $itoa64[$c1]; + $output .= $itoa64[$c2 & 0x3f]; + } while (1); + + return $output; + } +} + + +/***** End of BCrypt.php ***********/ diff --git a/application/libraries/Facebook.php b/application/libraries/Facebook.php new file mode 100644 index 0000000..c2582c7 --- /dev/null +++ b/application/libraries/Facebook.php @@ -0,0 +1,311 @@ +ins =& get_instance(); + $this->ins->load->config('facebook'); + $this->redirectUrl = $this->ins->config->item('redirect_url'); + $this->permissions = $this->ins->config->item('permissions'); + + + if(! isset($_SESSION)){ + session_start(); + } + } + // Check if user is logged in + + public function logged_in(){ + if(isset($_SESSION['accessToken'])){ + return TRUE; + }else{ + return FALSE; + } + } + + //return Facebook Class Object + public function getFb(){ + $fb = new Facebook\Facebook([ + + 'app_id' => $this->ins->config->item('api_id'), + 'app_secret' => $this->ins->config->item('api_secret'), + 'default_graph_version' => 'v2.5', + + ]); + return $fb; + } + + + //Getting Login URL + public function loginUrl(){ + $fb = $this->getFb(); + $this->helper = $fb->getRedirectLoginHelper(); + $loginUrl = $this->helper->getLoginUrl($this->redirectUrl, $this->permissions); + return $loginUrl; + + + } + + //setting new session + // + + public function setSession(){ + //checking if user is loged in + + $fb = $this->getFb(); + $helper = $fb->getRedirectLoginHelper(); + + try { + $accessToken = $helper->getAccessToken(); + } catch(Facebook\Exceptions\FacebookResponseException $e) { + // When Graph returns an error + //echo 'Graph returned an error: ' . $e->getMessage(); + } catch(Facebook\Exceptions\FacebookSDKException $e) { + // When validation fails or other local issues + //echo 'Facebook SDK returned an error: ' . $e->getMessage(); + } + + if (isset($accessToken)) { + // Logged in! + $_SESSION['accessToken'] = (string) $accessToken; + } elseif ($helper->getError()) { + + //user did not accept login. + + return FALSE; + } + + return TRUE; + } + + + //getting user profile data + + public function getProfile(){ + $fb = $this->getFb(); + try { + // Returns a `Facebook\FacebookResponse` object + $response = $fb->get('/me?fields=id,name,email,gender,timezone,verified,first_name,last_name,link',$_SESSION['accessToken']); + } catch(Facebook\Exceptions\FacebookResponseException $e) { + echo 'Graph returned an error: ' . $e->getMessage(); + exit; + } catch(Facebook\Exceptions\FacebookSDKException $e) { + echo 'Facebook SDK returned an error: ' . $e->getMessage(); + exit; + } + + $user = $response->getGraphUser(); + return $user; + + } + /** + * + * This funcion return user posts but it require "user_post" permissions first. + * + * @return [array] [user posts] + */ + public function getPosts(){ + $fb = $this->getFb(); + try { + // Returns a `Facebook\FacebookResponse` object + $response = $fb->get('/me/feed',$_SESSION['accessToken']); + } catch(Facebook\Exceptions\FacebookResponseException $e) { + echo 'Graph returned an error: ' . $e->getMessage(); + exit; + } catch(Facebook\Exceptions\FacebookSDKException $e) { + echo 'Facebook SDK returned an error: ' . $e->getMessage(); + exit; + } + + $posts = $response->getGraphEdge(); + return $posts; + + } + + /** + * Getting user friends. It require user_friends permissions + * It will only show friends who use this app. + * if no friend use this app, it will only return count of friends. + * + */ + + public function getFriends(){ + $fb = $this->getFb(); + try { + // Returns a `Facebook\FacebookResponse` object + $response = $fb->get('/me/friends?fields=id,name',$_SESSION['accessToken']); + } catch(Facebook\Exceptions\FacebookResponseException $e) { + echo 'Graph returned an error: ' . $e->getMessage(); + exit;} + catch(Facebook\Exceptions\FacebookSDKException $e) { + echo 'Facebook SDK returned an error: ' . $e->getMessage(); + exit; + } + + $friends = $response->getGraphEdge(); + return $friends; + + } + + /** + * @getting profile picture. + * + */ + public function getDp(){ + $fb = $this->getFb(); + try { + $uid = $this->getUserId(); + // Returns a `Facebook\FacebookResponse` object + $response = $fb->get('/me/picture?type=large&redirect=false',$_SESSION['accessToken']); + } catch(Facebook\Exceptions\FacebookResponseException $e) { + echo 'Graph returned an error: ' . $e->getMessage(); + exit;} + catch(Facebook\Exceptions\FacebookSDKException $e) { + echo 'Facebook SDK returned an error: ' . $e->getMessage(); + exit; + } + + $picture = $response->getGraphObject(); + $src = $picture['url']; + return $src; + + } + /** + * Updating status on user's wall. + * this function require 'publish_actions' permissions. + * usage: $this->facebook->updateStatus($message); + * author: Ahsan Shabbir (@ahsan044) + */ + public function updateStatus($message){ + $fb = $this->getFb(); + $response = $fb->post( '/me/feed', array( + 'message' => $message + ),$_SESSION['accessToken']); + + + } + /** + * Updating Images on user's wall. + * this function require 'publish_actions' permissions. + * usage: $this->facebook->uploadImage($message, $imagePath); + * example : $this->facebook->uploadImage('Uploading test Image', '/home/projects/Google.jpg'); + * author: Ahsan Shabbir (@ahsan044) + */ + + public function uploadImage($message,$imagePath){ + $fb = $this->getFb(); + + $data = [ + 'source' => $fb->fileToUpload($imagePath), + 'message' => $message + ]; + + $response = $fb->post('/me/photos', $data, $_SESSION['accessToken']); + if($response){ + return TRUE; + }else{ + return FALSE; + } + + } + + /** + * Video Upload Function + * usage : $this->facebook->uploadVideo($title, $descripion, $path_to_video); + * example: $this->facebook->uploadVideo('A Sample Video', 'Check this sample videoooo', '/home/user/video.mp4'); + * user require publish_actions to upload this video. + * + */ + public function uploadVideo($title, $description,$videoPath){ + $fb = $this->getFb(); + $data = [ + 'source' => $fb->fileToUpload($videoePath), + 'title' => $title, + 'description' => $description + ]; + + $response = $fb->post('/me/videos', $data, $_SESSION['accessToken']); + if($response){ + return TRUE; + }else{ + return FALSE; + } + + } + + + public function logoutUrl(){ + return $this->ins->config->item('logout_url'); + + } + + //logout user + public function logout(){ + if($this->logged_in()){ + unset($_SESSION['accessToken']); + redirect($this->logoutUrl()); + }else{ + redirect($this->logoutUrl()); + } + + } + + //getting userId of logged in user + public function getUserId(){ + $data = $this->getProfile(); + return $data['id']; + + } + } \ No newline at end of file diff --git a/application/libraries/Flexi_auth.php b/application/libraries/Flexi_auth.php new file mode 100644 index 0000000..00add50 --- /dev/null +++ b/application/libraries/Flexi_auth.php @@ -0,0 +1,1156 @@ +CI->load->model('flexi_auth_model'); + } + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + // LOGIN / VALIDATION FUNCTIONS + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * login + * Verifies a users identity and password, if valid, they are logged in. + * + * @return void + * @author Mathew Davies + */ + public function login($identity = FALSE, $password = FALSE, $remember_user = FALSE) + { + if ($this->CI->flexi_auth_model->login($identity, $password, $remember_user)) + { + $this->CI->flexi_auth_model->set_status_message('login_successful', 'config'); + return TRUE; + } + + // If no specific error message has been set, set a generic error. + if (! $this->CI->flexi_auth_model->error_messages()) + { + $this->CI->flexi_auth_model->set_error_message('login_unsuccessful', 'config'); + } + + return FALSE; + } + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * ip_login_attempts_exceeded + * Validates whether the number of failed login attempts from a unique IP address has exceeded a defined limit. + * The function can be used in conjunction with showing a Captcha for users repeatedly failing login attempts. + * + * @return bool + * @author Rob Hussey + */ + public function ip_login_attempts_exceeded() + { + return $this->CI->flexi_auth_model->ip_login_attempts_exceeded(); + } + + /** + * recaptcha + * Generates the html for Google reCAPTCHA. + * Note: If the reCAPTCHA is located on an SSL secured page (https), set $ssl = TRUE. + * + * @return string + * @author Rob Hussey + */ + public function recaptcha($ssl = FALSE) + { + return $this->CI->flexi_auth_model->recaptcha($ssl); + } + + /** + * validate_recaptcha + * Validates if a Google reCAPTCHA answer submitted via http POST data is correct. + * + * @return bool + * @author Rob Hussey + */ + public function validate_recaptcha() + { + return $this->CI->flexi_auth_model->validate_recaptcha(); + } + + /** + * math_captcha + * Generates a math captcha question and answer. + * The question is returned as a string, whilst the answer is set as a CI flash session. + * Use the 'validate_math_captcha()' function to validate the users submitted answer. + * + * @return string + * @author Rob Hussey + */ + public function math_captcha() + { + $captcha = $this->CI->flexi_auth_model->math_captcha(); + + $this->CI->session->set_flashdata($this->CI->auth->session_name['math_captcha'], $captcha['answer']); + + return $captcha['equation']; + } + + /** + * validate_math_captcha + * Validates if a submitted math captcha answer is correct. + * + * @return bool + * @author Rob Hussey + */ + public function validate_math_captcha($answer = FALSE) + { + return ($answer == $this->CI->session->flashdata($this->CI->auth->session_name['math_captcha'])); + } + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * min_password_length + * Gets the minimum valid password character length. + * + * @return int + * @author Rob Hussey + */ + public function min_password_length() + { + return $this->CI->auth->auth_security['min_password_length']; + } + + /** + * valid_password_chars + * Validate whether the submitted password only contains valid characters defined by the config file. + * + * @return bool + * @author Rob Hussey + */ + public function valid_password_chars($password = FALSE) + { + return (bool) preg_match("/^[".$this->CI->auth->auth_security['valid_password_chars']."]+$/i", $password); + } + + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + // USER TASK FUNCTIONS + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * activate_user + * Activates a users account allowing them to login to their account. + * If $verify_token = TRUE, a valid $activation_token must also be submitted. + * + * @return void + * @author Rob Hussey + */ + public function activate_user($user_id, $activation_token = FALSE, $verify_token = TRUE) + { + if ($this->CI->flexi_auth_model->activate_user($user_id, $activation_token, $verify_token)) + { + $this->CI->flexi_auth_model->set_status_message('activate_successful', 'config'); + return TRUE; + } + + $this->CI->flexi_auth_model->set_error_message('activate_unsuccessful', 'config'); + return FALSE; + } + + /** + * deactivate_user + * Deactivates a users account, preventing them from logging in. + * + * @return void + * @author Mathew Davies + */ + public function deactivate_user($user_id) + { + if ($this->CI->flexi_auth_model->deactivate_user($user_id)) + { + $this->CI->flexi_auth_model->set_status_message('deactivate_successful', 'config'); + return TRUE; + } + + $this->CI->flexi_auth_model->set_error_message('deactivate_unsuccessful', 'config'); + return FALSE; + } + + /** + * resend_activation_token + * Resends user a new activation token incase they have lost the previous one. + * + * @return bool + * @author Rob Hussey + */ + public function resend_activation_token($identity) + { + // Get primary identity. + $identity = $this->CI->flexi_auth_model->get_primary_identity($identity); + + if (empty($identity)) + { + $this->CI->flexi_auth_model->set_error_message('activation_email_unsuccessful', 'config'); + return FALSE; + } + + // Get user information. + $sql_select = array( + $this->CI->auth->tbl_col_user_account['id'], + $this->CI->auth->tbl_col_user_account['active'] + ); + + $sql_where[$this->CI->auth->primary_identity_col] = $identity; + + $user = $this->CI->flexi_auth_model->get_users($sql_select, $sql_where)->row(); + + $user_id = $user->{$this->CI->auth->database_config['user_acc']['columns']['id']}; + $active_status = $user->{$this->CI->auth->database_config['user_acc']['columns']['active']}; + + // If account is already activated. + if ($active_status == 1) + { + $this->CI->flexi_auth_model->set_status_message('account_already_activated', 'config'); + return TRUE; + } + // Else, run the deactivate_user() function to reset the users activation token. + else if ($this->CI->flexi_auth_model->deactivate_user($user_id)) + { + // Get user information. + $sql_select = array( + $this->CI->auth->primary_identity_col, + $this->CI->auth->tbl_col_user_account['activation_token'], + $this->CI->auth->tbl_col_user_account['email'] + ); + $sql_where[$this->CI->auth->primary_identity_col] = $identity; + $user = $this->CI->flexi_auth_model->get_users($sql_select, $sql_where)->row(); + + $email = $user->{$this->CI->auth->database_config['user_acc']['columns']['email']}; + $activation_token = $user->{$this->CI->auth->database_config['user_acc']['columns']['activation_token']}; + + // Set email data. + $email_to = $email; + $email_title = ' - Account Activation'; + + $user_data = array( + 'user_id' => $user_id, + 'identity' => $identity, + 'activation_token' => $activation_token + ); + $template = $this->CI->auth->email_settings['email_template_directory'].$this->CI->auth->email_settings['email_template_activate']; + + if ($this->CI->flexi_auth_model->send_email($email_to, $email_title, $user_data, $template)) + { + $this->CI->flexi_auth_model->set_status_message('activation_email_successful', 'config'); + return TRUE; + } + } + + $this->CI->flexi_auth_model->set_error_message('activation_email_unsuccessful', 'config'); + return FALSE; + } + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * validate_current_password + * Validates a submitted 'Current' password against the database for a specific user. + * + * @return bool + * @author Rob Hussey + */ + public function validate_current_password($current_password, $identity) + { + return ($this->CI->flexi_auth_model->verify_password($identity, $current_password)); + } + + /** + * change_password + * Validates a submitted 'Current' password against the database, if valid, the database is updated with the 'New' password. + * + * @return bool + * @author Mathew Davies + */ + public function change_password($identity, $current_password, $new_password) + { + if ($this->CI->flexi_auth_model->change_password($identity, $current_password, $new_password)) + { + $this->CI->flexi_auth_model->set_status_message('password_change_successful', 'config'); + return TRUE; + } + + $this->CI->flexi_auth_model->set_error_message('password_change_unsuccessful', 'config'); + return FALSE; + } + + /** + * forgotten_password + * Sends the user an email containing a link the user must click to verify they have requested to change their forgotten password. + * + * @return bool + * @author Rob Hussey + */ + public function forgotten_password($identifier) + { + // Get users primary identity. + if (!$identity = $this->CI->flexi_auth_model->get_primary_identity($identifier)) + { + $this->CI->flexi_auth_model->set_error_message('email_forgot_password_unsuccessful', 'config'); + return FALSE; + } + + if ($this->CI->flexi_auth_model->forgotten_password($identity)) + { + // Get user information. + $sql_select = array( + $this->CI->auth->tbl_col_user_account['id'], + $this->CI->auth->tbl_col_user_account['email'], + $this->CI->auth->tbl_col_user_account['forgot_password_token'] + ); + $sql_where[$this->CI->auth->primary_identity_col] = $identity; + + $user = $this->CI->flexi_auth_model->get_users($sql_select, $sql_where)->row(); + $user_id = $user->{$this->CI->auth->database_config['user_acc']['columns']['id']}; + $forgotten_password_token = $user->{$this->CI->auth->database_config['user_acc']['columns']['forgot_password_token']}; + + // Set email data. + $email_to = $user->{$this->CI->auth->database_config['user_acc']['columns']['email']}; + $email_title = ' - Forgotten Password Verification'; + + $user_data = array( + 'user_id' => $user_id, + 'identity' => $identity, + 'forgotten_password_token' => $forgotten_password_token + ); + $template = $this->CI->auth->email_settings['email_template_directory'].$this->CI->auth->email_settings['email_template_forgot_password']; + + if ($this->CI->flexi_auth_model->send_email($email_to, $email_title, $user_data, $template)) + { + $this->CI->flexi_auth_model->set_status_message('email_forgot_password_successful', 'config'); + return TRUE; + } + } + + $this->CI->flexi_auth_model->set_error_message('email_forgot_password_unsuccessful', 'config'); + return FALSE; + } + + /** + * validate_forgotten_password + * Validates a forgotten password token that was passed by clicking a link from a 'forgotten_password()' function email. + * + * @return void + * @author Rob Hussey + */ + public function validate_forgotten_password($user_id, $token) + { + return $this->CI->flexi_auth_model->validate_forgotten_password_token($user_id, $token); + } + + /** + * forgotten_password_complete + * This function is similar to the above 'validate_forgotten_password()' function, however, if validated the function also updates the database + * with a new password, then if defined via $send_email, an email will be sent to the user containing the new password. + * + * @return void + * @author Rob Hussey + */ + public function forgotten_password_complete($user_id, $forgot_password_token, $new_password = FALSE, $send_email = FALSE) + { + if ($this->CI->flexi_auth_model->validate_forgotten_password_token($user_id, $forgot_password_token)) + { + $sql_select = array( + $this->CI->auth->primary_identity_col, + $this->CI->auth->tbl_col_user_account['salt'], + $this->CI->auth->tbl_col_user_account['email'] + ); + + $sql_where[$this->CI->auth->tbl_col_user_account['id']] = $user_id; + + $user = $this->CI->flexi_auth_model->get_users($sql_select, $sql_where)->row(); + + if (!is_object($user)) + { + $this->CI->flexi_auth_model->set_error_message('password_change_unsuccessful', 'config'); + return FALSE; + } + + $identity = $user->{$this->CI->auth->db_settings['primary_identity_col']}; + $database_salt = $user->{$this->CI->auth->database_config['user_acc']['columns']['salt']}; + + // If no new password is set via $new_password, the function will generate a new one. + $new_password = $this->CI->flexi_auth_model->change_forgotten_password($user_id, $forgot_password_token, $new_password, $database_salt); + + // Send user email with new password if function variable $send_email = TRUE. + if ($send_email) + { + // Set email data + $email_to = $user->{$this->CI->auth->database_config['user_acc']['columns']['email']}; + $email_title = ' - New Password'; + + $user_data = array( + 'identity' => $identity, + 'new_password' => $new_password + ); + $template = $this->CI->auth->email_settings['email_template_directory']. + $this->CI->auth->email_settings['email_template_forgot_password_complete']; + + if ($this->CI->flexi_auth_model->send_email($email_to, $email_title, $user_data, $template)) + { + $this->CI->flexi_auth_model->set_status_message('email_new_password_successful', 'config'); + return TRUE; + } + } + // If new password is not set to be emailed, but has been successfully changed. + else if ($new_password) + { + $this->CI->flexi_auth_model->set_status_message('password_change_successful', 'config'); + return TRUE; + } + } + + $this->CI->flexi_auth_model->set_error_message('password_token_invalid', 'config'); + return FALSE; + } + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * update_email_via_verification + * Sends the user a verification email to their new email address, the user must then click a link within the email to update their accounts email address. + * This will help prevent a user accidentally locking themselves out of their account if they forget their password, as they can request a new password + * to be sent to their 'verified' email address rather than a misspelt email address. + * + * @return bool + * @author Rob Hussey + */ + public function update_email_via_verification($user_id, $new_email) + { + if ($this->CI->flexi_auth_model->set_update_email_token($user_id, $new_email)) + { + // Get user information. + $sql_select = array( + $this->CI->auth->tbl_col_user_account['email'], + $this->CI->auth->tbl_col_user_account['update_email_token'] + ); + $sql_where[$this->CI->auth->tbl_col_user_account['id']] = $user_id; + + $user = $this->CI->flexi_auth_model->get_users($sql_select, $sql_where)->row(); + + if (!is_object($user)) + { + $this->CI->flexi_auth_model->set_error_message('update_unsuccessful', 'config'); + return FALSE; + } + + $current_email = $user->{$this->CI->auth->database_config['user_acc']['columns']['email']}; + $update_email_token = $user->{$this->CI->auth->database_config['user_acc']['columns']['update_email_token']}; + + // Send email activation email. + $email_to = $new_email; + $email_title = ' - Email Change Verification'; + + $user_data = array( + 'user_id' => $user_id, + 'current_email' => $current_email, + 'new_email' => $new_email, + 'update_email_token' => $update_email_token + ); + + $template = $this->CI->auth->email_settings['email_template_directory'].$this->CI->auth->email_settings['email_template_update_email']; + + if ($this->CI->flexi_auth_model->send_email($email_to, $email_title, $user_data, $template)) + { + $this->CI->flexi_auth_model->set_status_message('email_activation_email_successful', 'config'); + return TRUE; + } + else + { + $this->CI->flexi_auth_model->set_error_message('email_activation_email_unsuccessful', 'config'); + return FALSE; + } + } + + $this->CI->flexi_auth_model->set_error_message('update_unsuccessful', 'config'); + return FALSE; + } + + /** + * verify_updated_email + * Verifies a submitted $update_email_token and updates their account with the new email address. + * + * @return bool + * @author Rob Hussey + */ + public function verify_updated_email($user_id, $update_email_token) + { + if ($this->CI->flexi_auth_model->verify_updated_email($user_id, $update_email_token)) + { + $this->CI->flexi_auth_model->set_status_message('update_successful', 'config'); + return TRUE; + } + + $this->CI->flexi_auth_model->set_error_message('update_unsuccessful', 'config'); + return FALSE; + } + + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + // USER MANAGEMENT / CRUD FUNCTIONS + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * insert_user + * Inserts user account and profile data, returning the users new id. + * + * @return void + * @author Rob Hussey + */ + public function insert_user($email, $username = FALSE, $password, $user_data, $group_id = FALSE, $activate = FALSE) + { + $user_id = $this->CI->flexi_auth_model->insert_user($email, $username, $password, $user_data, $group_id); + + if ($user_id) + { + // Check whether to auto activate the user. + if ($activate) + { + // If an account activation time limit is set by the config file, retain activation token. + $clear_token = ($this->CI->auth->auth_settings['account_activation_time_limit'] > 0) ? FALSE : TRUE; + + $this->CI->flexi_auth_model->activate_user($user_id, FALSE, FALSE, $clear_token); + } + + $sql_select = array( + $this->CI->auth->primary_identity_col, + $this->CI->auth->tbl_col_user_account['activation_token'] + ); + + $sql_where[$this->CI->auth->tbl_col_user_account['id']] = $user_id; + + $user = $this->CI->flexi_auth_model->get_users($sql_select, $sql_where)->row(); + + if (!is_object($user)) + { + $this->CI->flexi_auth_model->set_error_message('account_creation_unsuccessful', 'config'); + return FALSE; + } + + $identity = $user->{$this->CI->auth->db_settings['primary_identity_col']}; + $activation_token = $user->{$this->CI->auth->database_config['user_acc']['columns']['activation_token']}; + + // Prepare account activation email. + // If the $activation_token is not empty, the account must be activated via email before the user can login. + if (!empty($activation_token)) + { + // Set email data. + $email_to = $email; + $email_title = ' - Account Activation'; + + $user_data = array( + 'user_id' => $user_id, + 'identity' => $identity, + 'activation_token' => $activation_token + ); + $template = $this->CI->auth->email_settings['email_template_directory'].$this->CI->auth->email_settings['email_template_activate']; + + if ($this->CI->flexi_auth_model->send_email($email_to, $email_title, $user_data, $template)) + { + $this->CI->flexi_auth_model->set_status_message('activation_email_successful', 'config'); + return $user_id; + } + + $this->CI->flexi_auth_model->set_error_message('activation_email_unsuccessful', 'config'); + return FALSE; + } + + $this->CI->flexi_auth_model->set_status_message('account_creation_successful', 'config'); + return $user_id; + } + else + { + $this->CI->flexi_auth_model->set_error_message('account_creation_unsuccessful', 'config'); + return FALSE; + } + } + + /** + * update_user + * Updates the main user account table and any linked custom user tables with the submitted data. + * + * @return void + * @author Phil Sturgeon + */ + public function update_user($user_id, $user_data) + { + if ($this->CI->flexi_auth_model->update_user($user_id, $user_data)) + { + $this->CI->flexi_auth_model->set_status_message('update_successful', 'config'); + return TRUE; + } + + $this->CI->flexi_auth_model->set_error_message('update_unsuccessful', 'config'); + return FALSE; + } + + /** + * delete_user + * Deletes a user account and all linked custom user tables from the database. + * + * @return void + * @author Phil Sturgeon + */ + public function delete_user($user_id) + { + if ($this->CI->flexi_auth_model->delete_user($user_id)) + { + $this->CI->flexi_auth_model->set_status_message('delete_successful', 'config'); + return TRUE; + } + + $this->CI->flexi_auth_model->set_error_message('delete_unsuccessful', 'config'); + return FALSE; + } + + /** + * delete_unactivated_users + * Delete users that have not activated their account within a set time period. + * + * @return bool + * @author Rob Hussey + */ + public function delete_unactivated_users($inactive_days = 28, $sql_where = FALSE) + { + $users = $this->CI->flexi_auth_model + ->get_unactivated_users($inactive_days, $this->CI->auth->tbl_col_user_account['id'], $sql_where); + + if ($users->num_rows() > 0) + { + $users = $users->result_array(); + + foreach ($users as $user) + { + $user_id = $user[$this->CI->auth->database_config['user_acc']['columns']['id']]; + $this->CI->flexi_auth_model->delete_user($user_id); + } + $this->CI->flexi_auth_model->set_status_message('delete_successful', 'config'); + return TRUE; + } + + $this->CI->flexi_auth_model->set_error_message('delete_unsuccessful', 'config'); + return FALSE; + } + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * insert_custom_user_data + * Inserts data into a custom user table and returns the table name and row id of each record inserted. + * + * @return array/void + * @author Rob Hussey + */ + public function insert_custom_user_data($user_id = FALSE, $custom_data = FALSE) + { + if ($row_data = $this->CI->flexi_auth_model->insert_custom_user_data($user_id, $custom_data)) + { + $this->CI->flexi_auth_model->set_status_message('update_successful', 'config'); + return $row_data; + } + + $this->CI->flexi_auth_model->set_error_message('update_unsuccessful', 'config'); + return FALSE; + } + + /** + * update_custom_user_data + * Updates a custom user table with any submitted data. + * To identify which row to update, a table name and row id can be submitted, alternatively, data can be updated by submitting custom data + * that contains an array key and value of the primary key column and row id from any of the custom tables set via the config file. + * + * @return bool + * @author Rob Hussey + */ + public function update_custom_user_data($table = FALSE, $row_id = FALSE, $custom_data = FALSE) + { + if ($this->CI->flexi_auth_model->update_custom_user_data($table, $row_id, $custom_data)) + { + $this->CI->flexi_auth_model->set_status_message('update_successful', 'config'); + return TRUE; + } + + $this->CI->flexi_auth_model->set_error_message('update_unsuccessful', 'config'); + return FALSE; + } + + /** + * delete_custom_user_data + * Deletes a data row from a custom user table. + * + * @return void + * @author Rob Hussey + */ + public function delete_custom_user_data($table = FALSE, $row_id = FALSE) + { + if ($this->CI->flexi_auth_model->delete_custom_user_data($table, $row_id)) + { + $this->CI->flexi_auth_model->set_status_message('delete_successful', 'config'); + return TRUE; + } + + $this->CI->flexi_auth_model->set_error_message('delete_unsuccessful', 'config'); + return FALSE; + } + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * insert_group + * Inserts a new user group to the database. If the group has admin privileges this can be set using $is_admin = TRUE. + * + * @return void + * @author Rob Hussey + */ + public function insert_group($name, $description = NULL, $is_admin = FALSE, $custom_data = array()) + { + if ($group_id = $this->CI->flexi_auth_model->insert_group($name, $description, $is_admin, $custom_data)); + { + $this->CI->flexi_auth_model->set_status_message('update_successful','config'); + return $group_id; + } + + $this->CI->flexi_auth_model->set_error_message('update_unsuccessful', 'config'); + return FALSE; + } + + /** + * update_group + * Updates a user group with any submitted data. + * + * @return bool + * @author Rob Hussey + */ + public function update_group($group_id, $group_data) + { + if ($this->CI->flexi_auth_model->update_group($group_id, $group_data)) + { + $this->CI->flexi_auth_model->set_status_message('update_successful', 'config'); + return TRUE; + } + + $this->CI->flexi_auth_model->set_error_message('update_unsuccessful', 'config'); + return FALSE; + } + + /** + * delete_group + * Deletes a group from the user group table. + * + * @return bool + * @author Rob Hussey + */ + public function delete_group($sql_where) + { + if ($this->CI->flexi_auth_model->delete_group($sql_where)) + { + $this->CI->flexi_auth_model->set_status_message('delete_successful', 'config'); + return TRUE; + } + + $this->CI->flexi_auth_model->set_error_message('delete_unsuccessful', 'config'); + return FALSE; + } + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * insert_privilege + * Inserts a new user privilege to the database. + * + * @return void + * @author Rob Hussey + */ + public function insert_privilege($name, $description = NULL, $custom_data = array()) + { + if ($privilege_id = $this->CI->flexi_auth_model->insert_privilege($name, $description, $custom_data)); + { + $this->CI->flexi_auth_model->set_status_message('update_successful', 'config'); + return $privilege_id; + } + + $this->CI->flexi_auth_model->set_error_message('update_unsuccessful', 'config'); + return FALSE; + } + + /** + * update_privilege + * Updates a user privilege with any submitted data. + * + * @return bool + * @author Rob Hussey + */ + public function update_privilege($privilege_id, $privilege_data) + { + if ($this->CI->flexi_auth_model->update_privilege($privilege_id, $privilege_data)) + { + $this->CI->flexi_auth_model->set_status_message('update_successful', 'config'); + return TRUE; + } + + $this->CI->flexi_auth_model->set_error_message('update_unsuccessful', 'config'); + return FALSE; + } + + /** + * delete_privilege + * Deletes a privilege from the user privilege table. + * + * @return bool + * @author Rob Hussey + */ + public function delete_privilege($sql_where) + { + if ($this->CI->flexi_auth_model->delete_privilege($sql_where)) + { + $this->CI->flexi_auth_model->set_status_message('delete_successful', 'config'); + return TRUE; + } + + $this->CI->flexi_auth_model->set_error_message('delete_unsuccessful', 'config'); + return FALSE; + } + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * insert_privilege_user + * Inserts a new user privilege user to the database. + * + * @return void + * @author Rob Hussey + */ + public function insert_privilege_user($user_id, $privilege_id) + { + if ($privilege_id = $this->CI->flexi_auth_model->insert_privilege_user($user_id, $privilege_id)); + { + $this->CI->flexi_auth_model->set_status_message('update_successful', 'config'); + return $privilege_id; + } + + $this->CI->flexi_auth_model->set_error_message('update_unsuccessful', 'config'); + return FALSE; + } + + /** + * delete_privilege_user + * Deletes a user from the user privilege user table. + * + * @return bool + * @author Rob Hussey + */ + public function delete_privilege_user($sql_where) + { + if ($this->CI->flexi_auth_model->delete_privilege_user($sql_where)) + { + $this->CI->flexi_auth_model->set_status_message('delete_successful', 'config'); + return TRUE; + } + + $this->CI->flexi_auth_model->set_error_message('delete_unsuccessful', 'config'); + return FALSE; + } + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * insert_user_group_privilege + * Inserts a new user group privilege to the database. + * + * @return void + * @author Rob Hussey / Filou Tschiemer + */ + public function insert_user_group_privilege($group_id, $privilege_id) + { + if ($privilege_id = $this->CI->flexi_auth_model->insert_user_group_privilege($group_id, $privilege_id)); + { + $this->CI->flexi_auth_model->set_status_message('update_successful', 'config'); + return $privilege_id; + } + + $this->CI->flexi_auth_model->set_error_message('update_unsuccessful', 'config'); + return FALSE; + } + + /** + * delete_user_group_privilege + * Deletes a user group privilege from the user privilege group table. + * + * @return bool + * @author Rob Hussey / Filou Tschiemer + */ + public function delete_user_group_privilege($sql_where) + { + if ($this->CI->flexi_auth_model->delete_user_group_privilege($sql_where)) + { + $this->CI->flexi_auth_model->set_status_message('delete_successful', 'config'); + return TRUE; + } + + $this->CI->flexi_auth_model->set_error_message('delete_unsuccessful', 'config'); + return FALSE; + } + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * identity_available + * Returns whether a user identity is available in the database. + * The identity columns are defined via the $config['database']['settings']['identity_cols'] variable in the config file. + * + * @return bool + * @author Rob Hussey + */ + public function identity_available($identity = FALSE, $user_id = FALSE) + { + return $this->CI->flexi_auth_model->identity_available($identity, $user_id); + } + + /** + * email_available + * Returns whether an email address is available in the database. + * + * @return bool + * @author Rob Hussey + */ + public function email_available($email = FALSE, $user_id = FALSE) + { + return $this->CI->flexi_auth_model->email_available($email, $user_id); + } + + /** + * username_available + * Returns whether a username is available in the database. + * + * @return bool + * @author Rob Hussey + */ + public function username_available($username = FALSE, $user_id = FALSE) + { + return $this->CI->flexi_auth_model->username_available($username, $user_id); + } + + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + // GET USER / GROUP / PRIVILEGE FUNCTIONS + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * search_users_query + * Search user table using SQL WHERE 'x' LIKE '%y%' statement + * Note: Additional WHERE statements can be passed using the $sql_where parameter. + * + * @return object + * @author Rob Hussey + */ + public function search_users_query($search_query = FALSE, $exact_match = FALSE, $sql_select = FALSE, $sql_where = FALSE, $sql_group_by = TRUE) + { + return $this->CI->flexi_auth_model->search_users($search_query, $exact_match, $sql_select, $sql_where, $sql_group_by); + } + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * get_users_group_query + * Gets records from the user group table typically for a filtered set of users. + * + * @return object + * @author Rob Hussey + */ + public function get_users_group_query($sql_select = FALSE, $sql_where = FALSE) + { + $sql_select = ($sql_select) ? $sql_select : $this->CI->auth->tbl_user_group.'.*'; + + if (! $sql_where) + { + $sql_where = array($this->CI->auth->tbl_col_user_account['id'] => $this->CI->auth->session_data[$this->CI->auth->session_name['user_id']]); + } + + return $this->CI->flexi_auth_model->get_users($sql_select, $sql_where); + } + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * get_unactivated_users_query + * Get users that have not activated their account within a set time period. + * + * @return bool + * @author Rob Hussey + */ + public function get_unactivated_users_query($inactive_days = 28, $sql_select = FALSE, $sql_where = FALSE) + { + return $this->CI->flexi_auth_model->get_unactivated_users($inactive_days, $sql_select, $sql_where); + } + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * get_groups_query + * Returns a list of user groups matching the $sql_where condition. + * + * @return object + * @author Rob Hussey + */ + public function get_groups_query($sql_select = FALSE, $sql_where = FALSE) + { + return $this->CI->flexi_auth_model->get_groups($sql_select, $sql_where); + } + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * get_privileges_query + * Returns a list of user privileges matching the $sql_where condition. + * + * @return object + * @author Rob Hussey + */ + public function get_privileges_query($sql_select = FALSE, $sql_where = FALSE) + { + return $this->CI->flexi_auth_model->get_privileges($sql_select, $sql_where); + } + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * get_user_privileges_query + * Returns a users privileges using their session user_id by default. + * + * @return object + * @author Rob Hussey + */ + public function get_user_privileges_query($sql_select = FALSE, $sql_where = FALSE) + { + if (! $sql_where) + { + $sql_where = array($this->CI->auth->tbl_col_user_privilege_users['user_id'] => + $this->CI->auth->session_data[$this->CI->auth->session_name['user_id']]); + } + + return $this->CI->flexi_auth_model->get_user_privileges($sql_select, $sql_where); + } + + + /** + * get_user_group_privileges_query + * Returns a user groups privileges using a users session group_id by default. + * + * @return object + * @author Rob Hussey / Filou Tschiemer + */ + public function get_user_group_privileges_query($sql_select = FALSE, $sql_where = FALSE) + { + if (! $sql_where) + { + $sql_where = array($this->CI->auth->tbl_col_user_privilege_groups['group_id'] => + key($this->CI->auth->session_data[$this->CI->auth->session_name['group']])); + } + + return $this->CI->flexi_auth_model->get_user_group_privileges($sql_select, $sql_where); + } + + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + // EMAIL FUNCTIONS + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * send_email + * Emails a user a predefined email template. + * + * @return bool + * @author Rob Hussey + */ + public function send_email($email_to = FALSE, $email_title = FALSE, $template = FALSE, $email_data = array()) + { + if (!$email_to || !$template || empty($email_data)) + { + return FALSE; + } + + $template = $this->CI->auth->email_settings['email_template_directory'].$template; + + return $this->CI->flexi_auth_model->send_email($email_to, $email_title, $email_data, $template); + } + + /** + * template_data + * flexi auth sends emails for a number of functions, this function can set additional data variables that can then be used by the template files. + * + * @return void + * @author Rob Hussey + */ + public function template_data($template, $template_data) + { + if (empty($template) && empty($template_data)) + { + return FALSE; + } + + // Set template data placeholder. + $data = $this->CI->auth->template_data; + + // Change default template if set + if (!empty($template)) + { + $data['template'] = $template; + } + + // Add additional template data if set + if (!empty($template_data)) + { + $data['template_data'] = $template_data; + } + + $this->CI->auth->template_data = $data; + } +} + +/* End of file flexi_auth.php */ +/* Location: ./application/controllers/flexi_auth.php */ \ No newline at end of file diff --git a/application/libraries/Flexi_auth_lite.php b/application/libraries/Flexi_auth_lite.php new file mode 100644 index 0000000..300b214 --- /dev/null +++ b/application/libraries/Flexi_auth_lite.php @@ -0,0 +1,689 @@ +CI =& get_instance(); + + $this->CI->load->model('flexi_auth_lite_model'); + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + // CHECK LOGIN CREDENTIALS ON LOAD + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + // Validate login credentials on every page load if set via config file. + if ($this->is_logged_in() && $this->CI->auth->auth_security['validate_login_onload'] && !isset($this->CI->flexi_auth_lite_model->auth_verified)) + { + $this->CI->flexi_auth_lite_model->validate_database_login_session(); + } + // Auto log in the user if they have 'Remember me' cookies. + else if (!$this->is_logged_in() && get_cookie($this->CI->auth->cookie_name['user_id']) && + get_cookie($this->CI->auth->cookie_name['remember_series']) && get_cookie($this->CI->auth->cookie_name['remember_token'])) + { + $this->CI->load->model('flexi_auth_model'); + $this->CI->flexi_auth_model->login_remembered_user(); + } + } + + public function __call($method, $arguments) + { + $extension_types = array('_num_rows', '_row_array', '_array', '_result', '_row'); + $method_substr = str_replace(array_values($extension_types), FALSE, $method); + $method_substr_query = $method_substr.'_query'; + $method_substr_extension = str_replace($method_substr, FALSE, $method); + + // Get flexi auth class name. + $libraries = array('flexi_auth', 'flexi_auth_lite'); + foreach($libraries as $library) + { + if (isset($this->CI->$library)) + { + if (method_exists($this->CI->$library, $method_substr_query)) + { + $target_library = $library; + break; + } + } + } + + if (isset($target_library)) + { + // Pass the first 5 submitted arguments to the function (Usually the SQL SELECT and WHERE statements). + // Note: The search_users() function requires the 4th and 5th arguments. + $argument_1 = (isset($arguments[0])) ? $arguments[0] : FALSE; // Usually $sql_select + $argument_2 = (isset($arguments[1])) ? $arguments[1] : FALSE; // Usually $sql_where + $argument_3 = (isset($arguments[2])) ? $arguments[2] : FALSE; // Other + $argument_4 = (isset($arguments[3])) ? $arguments[3] : FALSE; // Other + $argument_5 = (isset($arguments[4])) ? $arguments[4] : FALSE; // Other + $data = $this->CI->$target_library->$method_substr_query($argument_1, $argument_2, $argument_3, $argument_4, $argument_5); + + if (! empty($data)) + { + if ($method_substr_extension == '_result') + { + return $data->result(); + } + else if ($method_substr_extension == '_row') + { + return $data->row(); + } + else if ($method_substr_extension == '_array') + { + return $data->result_array(); + } + else if ($method_substr_extension == '_row_array') + { + return $data->row_array(); + } + else if ($method_substr_extension == '_num_rows') + { + return $data->num_rows(); + } + else // '_query' + { + return $data; + } + } + } + + echo 'Call to an unknown method : "'.$method.'"'; + return FALSE; + } + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + // LOGOUT FUNCTION + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * logout + * Logs a user out of their account. + * Note: The $all_sessions variable allows you to define whether to delete all database sessions or just the current session. + * When set to FALSE, this can be used to logout a user off of one computer (Internet Cafe) but not another (Home). + * + * @return bool + * @author Rob Hussey + */ + public function logout($all_sessions = TRUE) + { + $this->CI->flexi_auth_lite_model->logout($all_sessions); + + $this->CI->flexi_auth_lite_model->set_status_message('logout_successful', 'config'); + + return TRUE; + } + + /** + * logout_specific_user + * Logs a specific user out of all of their logged in sessions. + * + * @return bool + * @author Rob Hussey + */ + public function logout_specific_user($user_id = FALSE) + { + $this->CI->flexi_auth_lite_model->logout_specific_user($user_id); + + $this->CI->flexi_auth_lite_model->set_status_message('logout_successful', 'config');#!# + + return TRUE; + } + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + // LOGIN STATUS FUNCTIONS + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * is_logged_in + * Verifies a user is logged in either via entering a valid password or using the 'Remember me' feature. + * + * @return bool + * @author Rob Hussey + */ + public function is_logged_in() + { + return (bool) $this->CI->auth->session_data[$this->CI->auth->session_name['user_identifier']]; + } + + /** + * is_logged_in_via_password + * Verifies a user has logged in via entering a valid password rather than using the 'Remember me' feature (Login by password is more secure). + * + * @return bool + * @author Rob Hussey + */ + public function is_logged_in_via_password() + { + return (bool) $this->CI->auth->session_data[$this->CI->auth->session_name['logged_in_via_password']]; + } + + /** + * is_admin + * Verifies a user belongs to a user group with admin permissions. + * + * @return bool + * @author Rob Hussey + */ + public function is_admin() + { + return (bool) $this->CI->auth->session_data[$this->CI->auth->session_name['is_admin']]; + } + + /** + * in_group + * Verifies whether a user is assigned to a particular user group, by comparing by either the group id or name. + * + * @return bool + * @author Rob Hussey + */ + public function in_group($groups = FALSE) + { + // Get users group and convert group name to lowercase for comparison. + $user_group = array(); + if (! empty($this->CI->auth->session_data[$this->CI->auth->session_name['group']])) + { + $session_group = $this->CI->auth->session_data[$this->CI->auth->session_name['group']]; + $user_group[key($session_group)] = strtolower(current($session_group)); + } + + // If multiple groups submitted as an array, loop through each. + if (is_array($groups)) + { + foreach($groups as $group) + { + if ((is_numeric($group) && $group == key($user_group)) || strtolower($group) == strtolower(current($user_group))) + { + return TRUE; + } + } + return FALSE; + } + + return ((is_numeric($groups) && $groups == key($user_group)) || strtolower($groups) == strtolower(current($user_group))); + } + + /** + * is_privileged + * Verifies whether a user has a specific privilege, by comparing by either privilege id or name. + * + * @return bool + * @author Rob Hussey + */ + public function is_privileged($privileges = FALSE) + { + // Get users privileges and convert names to lowercase for comparison. + $user_privileges = array(); + if (! empty($this->CI->auth->session_data[$this->CI->auth->session_name['privileges']])) + { + foreach($this->CI->auth->session_data[$this->CI->auth->session_name['privileges']] as $id => $name) + { + $user_privileges[$id] = strtolower($name); + } + } + + // If multiple groups submitted as an array, loop through each. + if (is_array($privileges)) + { + foreach($privileges as $privilege) + { + if ((is_numeric($privilege) && array_key_exists($privilege, $user_privileges)) || in_array(strtolower($privilege), $user_privileges)) + { + return TRUE; + } + } + return FALSE; + } + + return ((is_numeric($privileges) && array_key_exists($privileges, $user_privileges)) || in_array(strtolower($privileges), $user_privileges)); + } + + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + // GET USER FUNCTIONS + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * get_user_id + * Get the users id from the session. + * + * @return void + * @author Rob Hussey + */ + public function get_user_id() + { + return ($this->CI->auth->session_data[$this->CI->auth->session_name['user_id']] !== FALSE) ? + $this->CI->auth->session_data[$this->CI->auth->session_name['user_id']] : FALSE; + } + + /** + * get_user_identity + * Get the users primary identity from the session. + * + * @return void + * @author Rob Hussey + */ + public function get_user_identity() + { + return ($this->CI->auth->session_data[$this->CI->auth->session_name['user_identifier']] !== FALSE) ? + $this->CI->auth->session_data[$this->CI->auth->session_name['user_identifier']] : FALSE; + } + + /** + * get_user_group_id + * Get the users group id from the session. + * + * @return void + * @author Rob Hussey + */ + public function get_user_group_id() + { + return ($this->CI->auth->session_data[$this->CI->auth->session_name['group']] !== FALSE) ? + key($this->CI->auth->session_data[$this->CI->auth->session_name['group']]) : FALSE; + } + + /** + * get_user_group + * Get the users user group name from the session. + * + * @return void + * @author Rob Hussey + */ + public function get_user_group() + { + return ($this->CI->auth->session_data[$this->CI->auth->session_name['group']] !== FALSE) ? + current($this->CI->auth->session_data[$this->CI->auth->session_name['group']]) : FALSE; + } + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * get_user_by_id_query + * Gets data from the user account table and any custom tables that have been related to it by submitting the users id. + * + * @return object + * @author Rob Hussey + */ + public function get_user_by_id_query($user_id = FALSE, $sql_select = FALSE) + { + if (!is_numeric($user_id)) + { + $user_id = ($this->get_user_id()) ? $this->get_user_id() : NULL; + } + + $sql_where = array($this->CI->auth->tbl_col_user_account['id'] => $user_id); + + return $this->CI->flexi_auth_lite_model->get_users($sql_select, $sql_where); + } + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * get_user_by_identity_query + * Gets data from the user account table and any custom tables that have been related to it by submitting the users identity. + * + * @return object + * @author Rob Hussey + */ + public function get_user_by_identity_query($identity = FALSE, $sql_select = FALSE) + { + if (!$identity) + { + $identity = ($this->get_user_identity()) ? $this->get_user_identity() : NULL; + } + + $sql_where = array($this->CI->auth->primary_identity_col => $identity); + + return $this->CI->flexi_auth_lite_model->get_users($sql_select, $sql_where); + } + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * get_users_query + * Gets data from the user account table and any custom tables that have been related to it. + * Note: Query results will automatically be grouped by the user id (SQL GROUP BY). + * + * @return object + * @author Rob Hussey + */ + public function get_users_query($sql_select = FALSE, $sql_where = FALSE) + { + return $this->CI->flexi_auth_lite_model->get_users($sql_select, $sql_where); + } + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * get_custom_user_data_query + * Gets data from the user account table and any custom tables that have been related to it. + * Note: This function is nearly identical to 'get_users_query()' with the exception that the SQL GROUP BY statement can be defined. + * + * @return object + * @author Rob Hussey + */ + public function get_custom_user_data_query($sql_select = FALSE, $sql_where = FALSE, $sql_group_by = FALSE) + { + return $this->CI->flexi_auth_lite_model->get_users($sql_select, $sql_where, $sql_group_by); + } + + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + // REFERENCE DATABASE TABLE / COLUMN NAMES + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * db_table + * Returns a tables actual name by referencing the tables alias name defined via the config file. + * + * @return string + */ + function db_table($table = FALSE) + { + // Check the table exists in the config file and that a table name is set. + if (! isset($this->CI->auth->database_config[$table]['table']) || ! $this->CI->auth->database_config[$table]['table']) + { + return FALSE; + } + + return $this->CI->auth->database_config[$table]['table']; + } + + /** + * db_column + * Returns a table columns actual name by referencing the columns alias name defined via the config file. + * + * @return string + */ + function db_column($table = FALSE, $column = FALSE) + { + // Check the table and column exist in the config file and that a table/column name is set. + if (! isset($this->CI->auth->database_config[$table]['columns'][$column]) || ! $this->CI->auth->database_config[$table]['columns'][$column]) + { + return FALSE; + } + + return $this->CI->auth->database_config[$table]['columns'][$column]; + } + + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + // ACTIVE RECORD FUNCTIONS + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * The following functions set SQL statements using active record. + * The purpose of the functions is allow developers to create custom SELECT, JOIN, WHERE, ORDER BY, GROUP BY and LIMIT statements that are then + * applied in addition to the default statements set by functions within flexi auth. + * + * The functions should be called prior to calling any functions that get user or group data, or prior to using the login() function. + * Data can be submitted into each of the functions in the same manner as CI's AR functions (That share the same clause type). + * + * @author Rob Hussey + */ + + public function sql_select($columns = FALSE, $overwrite_existing = FALSE) + { + $this->CI->flexi_auth_lite_model->set_sql_to_var('select', $columns, FALSE, FALSE, $overwrite_existing); + } + + public function sql_where($column = FALSE, $value = FALSE, $overwrite_existing = FALSE) + { + $this->CI->flexi_auth_lite_model->set_sql_to_var('where', $column, $value, FALSE, $overwrite_existing); + } + + public function sql_or_where($column = FALSE, $value = FALSE, $overwrite_existing = FALSE) + { + $this->CI->flexi_auth_lite_model->set_sql_to_var('or_where', $column, $value, FALSE, $overwrite_existing); + } + + public function sql_where_in($column = FALSE, $value = FALSE, $overwrite_existing = FALSE) + { + $this->CI->flexi_auth_lite_model->set_sql_to_var('where_in', $column, $value, FALSE, $overwrite_existing); + } + + public function sql_or_where_in($column = FALSE, $value = FALSE, $overwrite_existing = FALSE) + { + $this->CI->flexi_auth_lite_model->set_sql_to_var('or_where_in', $column, $value, FALSE, $overwrite_existing); + } + + public function sql_where_not_in($column = FALSE, $value = FALSE, $overwrite_existing = FALSE) + { + $this->CI->flexi_auth_lite_model->set_sql_to_var('where_not_in', $column, $value, FALSE, $overwrite_existing); + } + + public function sql_or_where_not_in($column = FALSE, $value = FALSE, $overwrite_existing = FALSE) + { + $this->CI->flexi_auth_lite_model->set_sql_to_var('or_where_not_in', $column, $value, FALSE, $overwrite_existing); + } + + public function sql_like($column = FALSE, $value = FALSE, $wildcard_position = FALSE, $overwrite_existing = FALSE) + { + $this->CI->flexi_auth_lite_model->set_sql_to_var('like', $column, $value, $wildcard_position, $overwrite_existing); + } + + public function sql_or_like($column = FALSE, $value = FALSE, $wildcard_position = FALSE, $overwrite_existing = FALSE) + { + $this->CI->flexi_auth_lite_model->set_sql_to_var('or_like', $column, $value, $wildcard_position, $overwrite_existing); + } + + public function sql_not_like($column = FALSE, $value = FALSE, $wildcard_position = FALSE, $overwrite_existing = FALSE) + { + $this->CI->flexi_auth_lite_model->set_sql_to_var('not_like', $column, $value, $wildcard_position, $overwrite_existing); + } + + public function sql_or_not_like($column = FALSE, $value = FALSE, $wildcard_position = FALSE, $overwrite_existing = FALSE) + { + $this->CI->flexi_auth_lite_model->set_sql_to_var('or_not_like', $column, $value, $wildcard_position, $overwrite_existing); + } + + public function sql_join($column = FALSE, $join_on = FALSE, $join_type = FALSE, $overwrite_existing = FALSE) + { + $this->CI->flexi_auth_lite_model->set_sql_to_var('join', $column, $join_on, $join_type, $overwrite_existing); + } + + public function sql_order_by($column = FALSE, $sort_direction = FALSE, $overwrite_existing = FALSE) + { + $this->CI->flexi_auth_lite_model->set_sql_to_var('order_by', $column, $sort_direction, FALSE, $overwrite_existing); + } + + public function sql_group_by($columns = FALSE, $overwrite_existing = FALSE) + { + $this->CI->flexi_auth_lite_model->set_sql_to_var('group_by', $columns, FALSE, FALSE, $overwrite_existing); + } + + public function sql_limit($limit = FALSE, $offset = FALSE) + { + $this->CI->flexi_auth_lite_model->set_sql_to_var('limit', $limit, $offset); + } + + /** + * sql_clear + * Clears all current data stored in flexi auths SQL statements. + */ + public function sql_clear() + { + $this->CI->flexi_auth_lite_model->clear_arg_sql(); + } + + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + // MESSAGES AND ERRORS + ###++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++### + + /** + * set_status_message + * Set a status message to be displayed to user. + * + * @return void + * @author Rob Hussey + */ + public function set_status_message($status_message = FALSE, $target_user = 'public', $overwrite_existing = FALSE) + { + return $this->CI->flexi_auth_lite_model->set_status_message($status_message, $target_user, $overwrite_existing); + } + + /** + * clear_status_messages + * Clears all status messages. + * Get any status message(s) that may have been set by recently run functions. + * + * @return void + * @author Rob Hussey + */ + public function clear_status_messages() + { + $this->CI->auth->status_messages = array('public' => array(), 'admin' => array()); + return TRUE; + } + + /** + * status_messages + * Get any status message(s) that may have been set by recently run functions. + * + * @return void + * @author Rob Hussey + */ + public function status_messages($target_user = 'admin', $prefix_delimiter = FALSE, $suffix_delimiter = FALSE) + { + return $this->CI->flexi_auth_lite_model->status_messages($target_user, $prefix_delimiter, $suffix_delimiter); + } + + /** + * set_error_message + * Set an error message to be displayed to user. + * + * @return void + * @author Rob Hussey + */ + public function set_error_message($error_message, $target_user = 'admin', $overwrite_existing = FALSE) + { + // If $target_user exactly equals TRUE, set the target user as public. + $target_user = ($target_user === TRUE) ? 'public' : $target_user; + + return $this->CI->flexi_auth_lite_model->set_error_message($error_message, $target_user, $overwrite_existing); + } + + /** + * clear_error_messages + * Clears all error messages. + * + * @return void + * @author Rob Hussey + */ + public function clear_error_messages() + { + $this->CI->auth->error_messages = array('public' => array(), 'admin' => array()); + return TRUE; + } + + /** + * error_messages + * Get any error message(s) that may have been set by recently run functions. + * + * @return void + * @author Rob Hussey + */ + public function error_messages($target_user = 'admin', $prefix_delimiter = FALSE, $suffix_delimiter = FALSE) + { + return $this->CI->flexi_auth_lite_model->error_messages($target_user, $prefix_delimiter, $suffix_delimiter); + } + + /** + * clear_messages + * Clears all status and error messages. + * + * @return void + * @author Rob Hussey + */ + public function clear_messages() + { + $this->CI->auth->status_messages = array('public' => array(), 'admin' => array()); + $this->CI->auth->error_messages = array('public' => array(), 'admin' => array()); + return TRUE; + } + + /** + * get_messages_array + * Get any operational function messages and groups them into a status and error array. + * An additional array key named 'type' is also returned to clearly indicate what message types are returned. + * + * @return void + * @author Rob Hussey + */ + public function get_messages_array($target_user = 'admin', $prefix_delimiter = FALSE, $suffix_delimiter = FALSE) + { + $messages['status'] = $this->CI->flexi_auth_lite_model->status_messages($target_user, $prefix_delimiter, $suffix_delimiter); + $messages['errors'] = $this->CI->flexi_auth_lite_model->error_messages($target_user, $prefix_delimiter, $suffix_delimiter); + + // Set a message type identifier to state whether they are either status, error or mixed messages. + if (! empty($messages['status']) && empty($messages['errors'])) + { + $messages['type'] = 'status'; + } + else if (empty($messages['status']) && ! empty($messages['errors'])) + { + $messages['type'] = 'error'; + } + else if (! empty($messages['status']) && ! empty($messages['errors'])) + { + $messages['type'] = 'mixed'; + } + else + { + $messages['type'] = FALSE; + } + + // If message type is FALSE, no messages are set, so return FALSE. + return ($messages['type']) ? $messages : FALSE; + } + + /** + * get_messages + * Get any operational function messages whether of status or error type and format their output with delimiters. + * + * @return void + * @author Rob Hussey + */ + public function get_messages($target_user = 'admin', $prefix_delimiter = FALSE, $suffix_delimiter = FALSE) + { + $messages = $this->get_messages_array($target_user, $prefix_delimiter, $suffix_delimiter); + + return ($messages) ? $messages['status'].$messages['errors'] : FALSE; + } +} + +/* End of file flexi_auth_lite.php */ +/* Location: ./application/controllers/flexi_auth_lite.php */ \ No newline at end of file diff --git a/application/libraries/Format.php b/application/libraries/Format.php new file mode 100644 index 0000000..0d3bf77 --- /dev/null +++ b/application/libraries/Format.php @@ -0,0 +1,531 @@ +_CI = &get_instance(); + + // Load the inflector helper + $this->_CI->load->helper('inflector'); + + // If the provided data is already formatted we should probably convert it to an array + if ($from_type !== NULL) + { + if (method_exists($this, '_from_' . $from_type)) + { + $data = call_user_func([$this, '_from_' . $from_type], $data); + } + else + { + throw new Exception('Format class does not support conversion from "' . $from_type . '".'); + } + } + + // Set the member variable to the data passed + $this->_data = $data; + } + + /** + * Create an instance of the format class + * e.g: echo $this->format->factory(['foo' => 'bar'])->to_csv(); + * + * @param mixed $data Data to convert/parse + * @param string $from_type Type to convert from e.g. json, csv, html + * + * @return object Instance of the format class + */ + public function factory($data, $from_type = NULL) + { + // $class = __CLASS__; + // return new $class(); + + return new static($data, $from_type); + } + + // FORMATTING OUTPUT --------------------------------------------------------- + + /** + * Format data as an array + * + * @param mixed|NULL $data Optional data to pass, so as to override the data passed + * to the constructor + * @return array Data parsed as an array; otherwise, an empty array + */ + public function to_array($data = NULL) + { + // If no data is passed as a parameter, then use the data passed + // via the constructor + if ($data === NULL && func_num_args() === 0) + { + $data = $this->_data; + } + + // Cast as an array if not already + if (is_array($data) === FALSE) + { + $data = (array) $data; + } + + $array = []; + foreach ((array) $data as $key => $value) + { + if (is_object($value) === TRUE || is_array($value) === TRUE) + { + $array[$key] = $this->to_array($value); + } + else + { + $array[$key] = $value; + } + } + + return $array; + } + + /** + * Format data as XML + * + * @param mixed|NULL $data Optional data to pass, so as to override the data passed + * to the constructor + * @param NULL $structure + * @param string $basenode + * @return mixed + */ + public function to_xml($data = NULL, $structure = NULL, $basenode = 'xml') + { + if ($data === NULL && func_num_args() === 0) + { + $data = $this->_data; + } + + // turn off compatibility mode as simple xml throws a wobbly if you don't. + if (ini_get('zend.ze1_compatibility_mode') == 1) + { + ini_set('zend.ze1_compatibility_mode', 0); + } + + if ($structure === NULL) + { + $structure = simplexml_load_string("<$basenode />"); + } + + // Force it to be something useful + if (is_array($data) === FALSE && is_object($data) === FALSE) + { + $data = (array) $data; + } + + foreach ($data as $key => $value) + { + + //change false/true to 0/1 + if (is_bool($value)) + { + $value = (int) $value; + } + + // no numeric keys in our xml please! + if (is_numeric($key)) + { + // make string key... + $key = (singular($basenode) != $basenode) ? singular($basenode) : 'item'; + } + + // replace anything not alpha numeric + $key = preg_replace('/[^a-z_\-0-9]/i', '', $key); + + if ($key === '_attributes' && (is_array($value) || is_object($value))) + { + $attributes = $value; + if (is_object($attributes)) + { + $attributes = get_object_vars($attributes); + } + + foreach ($attributes as $attribute_name => $attribute_value) + { + $structure->addAttribute($attribute_name, $attribute_value); + } + } + // if there is another array found recursively call this function + elseif (is_array($value) || is_object($value)) + { + $node = $structure->addChild($key); + + // recursive call. + $this->to_xml($value, $node, $key); + } + else + { + // add single node. + $value = htmlspecialchars(html_entity_decode($value, ENT_QUOTES, 'UTF-8'), ENT_QUOTES, 'UTF-8'); + + $structure->addChild($key, $value); + } + } + + return $structure->asXML(); + } + + /** + * Format data as HTML + * + * @param mixed|NULL $data Optional data to pass, so as to override the data passed + * to the constructor + * @return mixed + */ + public function to_html($data = NULL) + { + // If no data is passed as a parameter, then use the data passed + // via the constructor + if ($data === NULL && func_num_args() === 0) + { + $data = $this->_data; + } + + // Cast as an array if not already + if (is_array($data) === FALSE) + { + $data = (array) $data; + } + + // Check if it's a multi-dimensional array + if (isset($data[0]) && count($data) !== count($data, COUNT_RECURSIVE)) + { + // Multi-dimensional array + $headings = array_keys($data[0]); + } + else + { + // Single array + $headings = array_keys($data); + $data = [$data]; + } + + // Load the table library + $this->_CI->load->library('table'); + + $this->_CI->table->set_heading($headings); + + foreach ($data as $row) + { + // Suppressing the "array to string conversion" notice + // Keep the "evil" @ here + $row = @array_map('strval', $row); + + $this->_CI->table->add_row($row); + } + + return $this->_CI->table->generate(); + } + + /** + * @link http://www.metashock.de/2014/02/create-csv-file-in-memory-php/ + * @param mixed|NULL $data Optional data to pass, so as to override the data passed + * to the constructor + * @param string $delimiter The optional delimiter parameter sets the field + * delimiter (one character only). NULL will use the default value (,) + * @param string $enclosure The optional enclosure parameter sets the field + * enclosure (one character only). NULL will use the default value (") + * @return string A csv string + */ + public function to_csv($data = NULL, $delimiter = ',', $enclosure = '"') + { + // Use a threshold of 1 MB (1024 * 1024) + $handle = fopen('php://temp/maxmemory:1048576', 'w'); + if ($handle === FALSE) + { + return NULL; + } + + // If no data is passed as a parameter, then use the data passed + // via the constructor + if ($data === NULL && func_num_args() === 0) + { + $data = $this->_data; + } + + // If NULL, then set as the default delimiter + if ($delimiter === NULL) + { + $delimiter = ','; + } + + // If NULL, then set as the default enclosure + if ($enclosure === NULL) + { + $enclosure = '"'; + } + + // Cast as an array if not already + if (is_array($data) === FALSE) + { + $data = (array) $data; + } + + // Check if it's a multi-dimensional array + if (isset($data[0]) && count($data) !== count($data, COUNT_RECURSIVE)) + { + // Multi-dimensional array + $headings = array_keys($data[0]); + } + else + { + // Single array + $headings = array_keys($data); + $data = [$data]; + } + + // Apply the headings + fputcsv($handle, $headings, $delimiter, $enclosure); + + foreach ($data as $record) + { + // If the record is not an array, then break. This is because the 2nd param of + // fputcsv() should be an array + if (is_array($record) === FALSE) + { + break; + } + + // Suppressing the "array to string conversion" notice. + // Keep the "evil" @ here. + $record = @ array_map('strval', $record); + + // Returns the length of the string written or FALSE + fputcsv($handle, $record, $delimiter, $enclosure); + } + + // Reset the file pointer + rewind($handle); + + // Retrieve the csv contents + $csv = stream_get_contents($handle); + + // Close the handle + fclose($handle); + + return $csv; + } + + /** + * Encode data as json + * + * @param mixed|NULL $data Optional data to pass, so as to override the data passed + * to the constructor + * @return string Json representation of a value + */ + public function to_json($data = NULL) + { + // If no data is passed as a parameter, then use the data passed + // via the constructor + if ($data === NULL && func_num_args() === 0) + { + $data = $this->_data; + } + + // Get the callback parameter (if set) + $callback = $this->_CI->input->get('callback'); + + if (empty($callback) === TRUE) + { + return json_encode($data); + } + + // We only honour a jsonp callback which are valid javascript identifiers + elseif (preg_match('/^[a-z_\$][a-z0-9\$_]*(\.[a-z_\$][a-z0-9\$_]*)*$/i', $callback)) + { + // Return the data as encoded json with a callback + return $callback . '(' . json_encode($data) . ');'; + } + + // An invalid jsonp callback function provided. + // Though I don't believe this should be hardcoded here + $data['warning'] = 'INVALID JSONP CALLBACK: ' . $callback; + + return json_encode($data); + } + + /** + * Encode data as a serialized array + * + * @param mixed|NULL $data Optional data to pass, so as to override the data passed + * to the constructor + * @return string Serialized data + */ + public function to_serialized($data = NULL) + { + // If no data is passed as a parameter, then use the data passed + // via the constructor + if ($data === NULL && func_num_args() === 0) + { + $data = $this->_data; + } + + return serialize($data); + } + + /** + * Format data using a PHP structure + * + * @param mixed|NULL $data Optional data to pass, so as to override the data passed + * to the constructor + * @return mixed String representation of a variable + */ + public function to_php($data = NULL) + { + // If no data is passed as a parameter, then use the data passed + // via the constructor + if ($data === NULL && func_num_args() === 0) + { + $data = $this->_data; + } + + return var_export($data, TRUE); + } + + // INTERNAL FUNCTIONS + + /** + * @param $data XML string + * @return SimpleXMLElement XML element object; otherwise, empty array + */ + protected function _from_xml($data) + { + return $data ? (array) simplexml_load_string($data, 'SimpleXMLElement', LIBXML_NOCDATA) : []; + } + + /** + * @param string $data CSV string + * @param string $delimiter The optional delimiter parameter sets the field + * delimiter (one character only). NULL will use the default value (,) + * @param string $enclosure The optional enclosure parameter sets the field + * enclosure (one character only). NULL will use the default value (") + * @return array A multi-dimensional array with the outer array being the number of rows + * and the inner arrays the individual fields + */ + protected function _from_csv($data, $delimiter = ',', $enclosure = '"') + { + // If NULL, then set as the default delimiter + if ($delimiter === NULL) + { + $delimiter = ','; + } + + // If NULL, then set as the default enclosure + if ($enclosure === NULL) + { + $enclosure = '"'; + } + + return str_getcsv($data, $delimiter, $enclosure); + } + + /** + * @param $data Encoded json string + * @return mixed Decoded json string with leading and trailing whitespace removed + */ + protected function _from_json($data) + { + return json_decode(trim($data)); + } + + /** + * @param string Data to unserialized + * @return mixed Unserialized data + */ + protected function _from_serialize($data) + { + return unserialize(trim($data)); + } + + /** + * @param $data Data to trim leading and trailing whitespace + * @return string Data with leading and trailing whitespace removed + */ + protected function _from_php($data) + { + return trim($data); + } + +} diff --git a/application/libraries/HybridAuthLib.php b/application/libraries/HybridAuthLib.php new file mode 100644 index 0000000..81ee277 --- /dev/null +++ b/application/libraries/HybridAuthLib.php @@ -0,0 +1,34 @@ +load->helper('url_helper'); + + $config['base_url'] = site_url((config_item('index_page') == '' ? SELF : '').$config['base_url']); + + parent::__construct($config); + + log_message('debug', 'HybridAuthLib Class Initalized'); + } + + /** + * @deprecated + */ + public static function serviceEnabled($service) + { + return self::providerEnabled($service); + } + + public static function providerEnabled($provider) + { + return isset(parent::$config['providers'][$provider]) && parent::$config['providers'][$provider]['enabled']; + } +} + +/* End of file HybridAuthLib.php */ +/* Location: ./application/libraries/HybridAuthLib.php */ \ No newline at end of file diff --git a/application/libraries/IP2Location_lib.php b/application/libraries/IP2Location_lib.php new file mode 100644 index 0000000..cb2fe82 --- /dev/null +++ b/application/libraries/IP2Location_lib.php @@ -0,0 +1,102 @@ +lookup(self::getIP($ip), \IP2Location\Database::COUNTRY_CODE); + } + + public function getCountryName($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::COUNTRY_NAME); + } + + public function getRegionName($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::REGION_NAME); + } + + public function getCityName($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::CITY_NAME); + } + + public function getLatitude($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::LATITUDE); + } + + public function getLongitude($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::LONGITUDE); + } + + public function getISP($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::ISP); + } + + public function getDomainName($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::DOMAIN_NAME); + } + + public function getZIPCode($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::ZIP_CODE); + } + + public function getTimeZone($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::TIME_ZONE); + } + + public function getNetSpeed($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::NET_SPEED); + } + public function getIDDCode($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::IDD_CODE); + } + + public function getAreaCode($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::AREA_CODE); + } + + public function getWeatherStationCode($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::WEATHER_STATION_CODE); + } + + public function getWeatherStationName($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::WEATHER_STATION_NAME); + } + + public function getMCC($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::MCC); + } + + public function getMNC($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::MNC); + } + + public function getMobileCarrierName($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::MOBILE_CARRIER_NAME); + } + + public function getElevation($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::ELEVATION); + } + + public function getUsageType($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::USAGE_TYPE); + } + + protected function getIP($ip=NULL) { + return ($ip) ? $ip : $_SERVER['REMOTE_ADDR']; + } +} +?> diff --git a/application/libraries/Ion_auth.php b/application/libraries/Ion_auth.php new file mode 100644 index 0000000..d6073e4 --- /dev/null +++ b/application/libraries/Ion_auth.php @@ -0,0 +1,543 @@ +config->load('ion_auth', TRUE); + $this->load->library(array('email')); + $this->lang->load('ion_auth'); + $this->load->helper(array('cookie', 'language','url')); + + $this->load->library('session'); + + $this->load->model('auth/Ion_auth_model', 'ion_auth_model'); // fix for HMVC + + $this->_cache_user_in_group =& $this->ion_auth_model->_cache_user_in_group; + + $email_config = $this->config->item('email_config', 'ion_auth'); + + if ($this->config->item('use_ci_email', 'ion_auth') && isset($email_config) && is_array($email_config)) + { + $this->email->initialize($email_config); + } + + $this->ion_auth_model->trigger_events('library_constructor'); + } + + /** + * __call + * + * Acts as a simple way to call model methods without loads of stupid alias' + * + * @param $method + * @param $arguments + * @return mixed + * @throws Exception + */ + public function __call($method, $arguments) + { + if (!method_exists( $this->ion_auth_model, $method) ) + { + throw new Exception('Undefined method Ion_auth::' . $method . '() called'); + } + if($method == 'create_user') + { + return call_user_func_array(array($this, 'register'), $arguments); + } + if($method=='update_user') + { + return call_user_func_array(array($this, 'update'), $arguments); + } + return call_user_func_array( array($this->ion_auth_model, $method), $arguments); + } + + /** + * __get + * + * Enables the use of CI super-global without having to define an extra variable. + * + * I can't remember where I first saw this, so thank you if you are the original author. -Militis + * + * @access public + * @param $var + * @return mixed + */ + public function __get($var) + { + return get_instance()->$var; + } + + + /** + * forgotten password feature + * + * @param $identity + * @return mixed boolean / array + * @author Mathew + */ + public function forgotten_password($identity) //changed $email to $identity + { + if ( $this->ion_auth_model->forgotten_password($identity) ) //changed + { + // Get user information + $identifier = $this->ion_auth_model->identity_column; // use model identity column, so it can be overridden in a controller + $user = $this->where($identifier, $identity)->where('active', 1)->users()->row(); // changed to get_user_by_identity from email + + if ($user) + { + $data = array( + 'identity' => $user->{$this->config->item('identity', 'ion_auth')}, + 'forgotten_password_code' => $user->forgotten_password_code + ); + + if(!$this->config->item('use_ci_email', 'ion_auth')) + { + $this->set_message('forgot_password_successful'); + return $data; + } + else + { + $message = $this->load->view($this->config->item('email_templates', 'ion_auth').$this->config->item('email_forgot_password', 'ion_auth'), $data, true); + $this->email->clear(); + $this->email->from($this->config->item('admin_email', 'ion_auth'), $this->config->item('site_title', 'ion_auth')); + $this->email->to($user->email); + $this->email->subject($this->config->item('site_title', 'ion_auth') . ' - ' . $this->lang->line('email_forgotten_password_subject')); + $this->email->message($message); + + if ($this->email->send()) + { + $this->set_message('forgot_password_successful'); + return TRUE; + } + else + { + $this->set_error('forgot_password_unsuccessful'); + return FALSE; + } + } + } + else + { + $this->set_error('forgot_password_unsuccessful'); + return FALSE; + } + } + else + { + $this->set_error('forgot_password_unsuccessful'); + return FALSE; + } + } + + /** + * forgotten_password_complete + * + * @param $code + * @author Mathew + * @return bool + */ + public function forgotten_password_complete($code) + { + $this->ion_auth_model->trigger_events('pre_password_change'); + + $identity = $this->config->item('identity', 'ion_auth'); + $profile = $this->where('forgotten_password_code', $code)->users()->row(); //pass the code to profile + + if (!$profile) + { + $this->ion_auth_model->trigger_events(array('post_password_change', 'password_change_unsuccessful')); + $this->set_error('password_change_unsuccessful'); + return FALSE; + } + + $new_password = $this->ion_auth_model->forgotten_password_complete($code, $profile->salt); + + if ($new_password) + { + $data = array( + 'identity' => $profile->{$identity}, + 'new_password' => $new_password + ); + if(!$this->config->item('use_ci_email', 'ion_auth')) + { + $this->set_message('password_change_successful'); + $this->ion_auth_model->trigger_events(array('post_password_change', 'password_change_successful')); + return $data; + } + else + { + $message = $this->load->view($this->config->item('email_templates', 'ion_auth').$this->config->item('email_forgot_password_complete', 'ion_auth'), $data, true); + + $this->email->clear(); + $this->email->from($this->config->item('admin_email', 'ion_auth'), $this->config->item('site_title', 'ion_auth')); + $this->email->to($profile->email); + $this->email->subject($this->config->item('site_title', 'ion_auth') . ' - ' . $this->lang->line('email_new_password_subject')); + $this->email->message($message); + + if ($this->email->send()) + { + $this->set_message('password_change_successful'); + $this->ion_auth_model->trigger_events(array('post_password_change', 'password_change_successful')); + return TRUE; + } + else + { + $this->set_error('password_change_unsuccessful'); + $this->ion_auth_model->trigger_events(array('post_password_change', 'password_change_unsuccessful')); + return FALSE; + } + + } + } + + $this->ion_auth_model->trigger_events(array('post_password_change', 'password_change_unsuccessful')); + return FALSE; + } + + /** + * forgotten_password_check + * + * @param $code + * @author Michael + * @return bool + */ + public function forgotten_password_check($code) + { + $profile = $this->where('forgotten_password_code', $code)->users()->row(); //pass the code to profile + + if (!is_object($profile)) + { + $this->set_error('password_change_unsuccessful'); + return FALSE; + } + else + { + if ($this->config->item('forgot_password_expiration', 'ion_auth') > 0) { + //Make sure it isn't expired + $expiration = $this->config->item('forgot_password_expiration', 'ion_auth'); + if (time() - $profile->forgotten_password_time > $expiration) { + //it has expired + $this->clear_forgotten_password_code($code); + $this->set_error('password_change_unsuccessful'); + return FALSE; + } + } + return $profile; + } + } + + /** + * register + * + * @param $identity + * @param $password + * @param $email + * @param array $additional_data + * @param array $group_ids + * @author Mathew + * @return bool + */ + public function register($identity, $password, $email, $additional_data = array(), $group_ids = array()) //need to test email activation + { + $this->ion_auth_model->trigger_events('pre_account_creation'); + + $email_activation = $this->config->item('email_activation', 'ion_auth'); + + $id = $this->ion_auth_model->register($identity, $password, $email, $additional_data, $group_ids); + + if (!$email_activation) + { + if ($id !== FALSE) + { + $this->set_message('account_creation_successful'); + $this->ion_auth_model->trigger_events(array('post_account_creation', 'post_account_creation_successful')); + return $id; + } + else + { + $this->set_error('account_creation_unsuccessful'); + $this->ion_auth_model->trigger_events(array('post_account_creation', 'post_account_creation_unsuccessful')); + return FALSE; + } + } + else + { + if (!$id) + { + $this->set_error('account_creation_unsuccessful'); + return FALSE; + } + + // deactivate so the user much follow the activation flow + $deactivate = $this->ion_auth_model->deactivate($id); + + // the deactivate method call adds a message, here we need to clear that + $this->ion_auth_model->clear_messages(); + + + if (!$deactivate) + { + $this->set_error('deactivate_unsuccessful'); + $this->ion_auth_model->trigger_events(array('post_account_creation', 'post_account_creation_unsuccessful')); + return FALSE; + } + + $activation_code = $this->ion_auth_model->activation_code; + $identity = $this->config->item('identity', 'ion_auth'); + $user = $this->ion_auth_model->user($id)->row(); + + $data = array( + 'identity' => $user->{$identity}, + 'id' => $user->id, + 'email' => $email, + 'activation' => $activation_code, + ); + if(!$this->config->item('use_ci_email', 'ion_auth')) + { + $this->ion_auth_model->trigger_events(array('post_account_creation', 'post_account_creation_successful', 'activation_email_successful')); + $this->set_message('activation_email_successful'); + return $data; + } + else + { + $message = $this->load->view($this->config->item('email_templates', 'ion_auth').$this->config->item('email_activate', 'ion_auth'), $data, true); + + $this->email->clear(); + $this->email->from($this->config->item('admin_email', 'ion_auth'), $this->config->item('site_title', 'ion_auth')); + $this->email->to($email); + $this->email->subject($this->config->item('site_title', 'ion_auth') . ' - ' . $this->lang->line('email_activation_subject')); + $this->email->message($message); + + if ($this->email->send() == TRUE) + { + $this->ion_auth_model->trigger_events(array('post_account_creation', 'post_account_creation_successful', 'activation_email_successful')); + $this->set_message('activation_email_successful'); + return $id; + } + + } + + $this->ion_auth_model->trigger_events(array('post_account_creation', 'post_account_creation_unsuccessful', 'activation_email_unsuccessful')); + $this->set_error('activation_email_unsuccessful'); + return FALSE; + } + } + + /** + * logout + * + * @return void + * @author Mathew + **/ + public function logout() + { + $this->ion_auth_model->trigger_events('logout'); + + $identity = $this->config->item('identity', 'ion_auth'); + + if (substr(CI_VERSION, 0, 1) == '2') + { + $this->session->unset_userdata( array($identity => '', 'id' => '', 'user_id' => '') ); + } + else + { + $this->session->unset_userdata( array($identity, 'id', 'user_id') ); + } + + // delete the remember me cookies if they exist + if (get_cookie($this->config->item('identity_cookie_name', 'ion_auth'))) + { + delete_cookie($this->config->item('identity_cookie_name', 'ion_auth')); + } + if (get_cookie($this->config->item('remember_cookie_name', 'ion_auth'))) + { + delete_cookie($this->config->item('remember_cookie_name', 'ion_auth')); + } + + // Destroy the session + $this->session->sess_destroy(); + + //Recreate the session + if (substr(CI_VERSION, 0, 1) == '2') + { + $this->session->sess_create(); + } + else + { + if (version_compare(PHP_VERSION, '7.0.0') >= 0) { + session_start(); + } + $this->session->sess_regenerate(TRUE); + } + + $this->set_message('logout_successful'); + return TRUE; + } + + /** + * logged_in + * + * @return bool + * @author Mathew + **/ + public function logged_in() + { + $this->ion_auth_model->trigger_events('logged_in'); + + $recheck= $this->ion_auth_model->recheck_session(); + + //auto-login the user if they are remembered + if ( ! $recheck && get_cookie($this->config->item('identity_cookie_name', 'ion_auth')) && get_cookie($this->config->item('remember_cookie_name', 'ion_auth'))) + { + $recheck = $this->ion_auth_model->login_remembered_user(); + } + + return $recheck; + } + + /** + * logged_in + * + * @return integer + * @author jrmadsen67 + **/ + public function get_user_id() + { + $user_id = $this->session->userdata('user_id'); + if (!empty($user_id)) + { + return $user_id; + } + return null; + } + + + /** + * is_admin + * + * @return bool + * @author Ben Edmunds + **/ + public function is_admin($id=false) + { + $this->ion_auth_model->trigger_events('is_admin'); + + $admin_group = $this->config->item('admin_group', 'ion_auth'); + + return $this->in_group($admin_group, $id); + } + + /** + * in_group + * + * @param mixed group(s) to check + * @param bool user id + * @param bool check if all groups is present, or any of the groups + * + * @return bool + * @author Phil Sturgeon + **/ + public function in_group($check_group, $id=false, $check_all = false) + { + $this->ion_auth_model->trigger_events('in_group'); + + $id || $id = $this->session->userdata('user_id'); + + if (!is_array($check_group)) + { + $check_group = array($check_group); + } + + if (isset($this->_cache_user_in_group[$id])) + { + $groups_array = $this->_cache_user_in_group[$id]; + } + else + { + $users_groups = $this->ion_auth_model->get_users_groups($id)->result(); + $groups_array = array(); + foreach ($users_groups as $group) + { + $groups_array[$group->id] = $group->name; + } + $this->_cache_user_in_group[$id] = $groups_array; + } + foreach ($check_group as $key => $value) + { + $groups = (is_string($value)) ? $groups_array : array_keys($groups_array); + + /** + * if !all (default), in_array + * if all, !in_array + */ + if (in_array($value, $groups) xor $check_all) + { + /** + * if !all (default), true + * if all, false + */ + return !$check_all; + } + } + + /** + * if !all (default), false + * if all, true + */ + return $check_all; + } + +} diff --git a/application/libraries/Ip2location.php b/application/libraries/Ip2location.php new file mode 100644 index 0000000..b91b333 --- /dev/null +++ b/application/libraries/Ip2location.php @@ -0,0 +1,102 @@ +lookup(self::getIP($ip), \IP2Location\Database::COUNTRY_CODE); + } + + public function getCountryName($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::COUNTRY_NAME); + } + + public function getRegionName($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::REGION_NAME); + } + + public function getCityName($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::CITY_NAME); + } + + public function getLatitude($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::LATITUDE); + } + + public function getLongitude($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::LONGITUDE); + } + + public function getISP($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::ISP); + } + + public function getDomainName($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::DOMAIN_NAME); + } + + public function getZIPCode($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::ZIP_CODE); + } + + public function getTimeZone($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::TIME_ZONE); + } + + public function getNetSpeed($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::NET_SPEED); + } + public function getIDDCode($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::IDD_CODE); + } + + public function getAreaCode($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::AREA_CODE); + } + + public function getWeatherStationCode($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::WEATHER_STATION_CODE); + } + + public function getWeatherStationName($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::WEATHER_STATION_NAME); + } + + public function getMCC($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::MCC); + } + + public function getMNC($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::MNC); + } + + public function getMobileCarrierName($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::MOBILE_CARRIER_NAME); + } + + public function getElevation($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::ELEVATION); + } + + public function getUsageType($ip=NULL) { + return self::$ip2location->lookup(self::getIP($ip), \IP2Location\Database::USAGE_TYPE); + } + + protected function getIP($ip=NULL) { + return ($ip) ? $ip : $_SERVER['REMOTE_ADDR']; + } +} +?> diff --git a/application/libraries/Ip2location.phps b/application/libraries/Ip2location.phps new file mode 100644 index 0000000..6c34fef --- /dev/null +++ b/application/libraries/Ip2location.phps @@ -0,0 +1,1904 @@ +. + * + */ + +namespace IP2Location; + +/** + * IP2Location database class + * + */ +class Database { + + /** + * Current module's version + * + * @var string + */ + const VERSION = '8.0.2'; + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Error field constants /////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Unsupported field message + * + * @var string + */ + const FIELD_NOT_SUPPORTED = 'This parameter is unavailable in selected .BIN data file. Please upgrade.'; + + /** + * Unknown field message + * + * @var string + */ + const FIELD_NOT_KNOWN = 'This parameter is inexistent. Please verify.'; + + /** + * Invalid IP address message + * + * @var string + */ + const INVALID_IP_ADDRESS = 'Invalid IP address.'; + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Field selection constants /////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Country code (ISO 3166-1 Alpha 2) + * + * @var int + */ + const COUNTRY_CODE = 1; + + /** + * Country name + * + * @var int + */ + const COUNTRY_NAME = 2; + + /** + * Region name + * + * @var int + */ + const REGION_NAME = 3; + + /** + * City name + * + * @var int + */ + const CITY_NAME = 4; + + /** + * Latitude + * + * @var int + */ + const LATITUDE = 5; + + /** + * Longitude + * + * @var int + */ + const LONGITUDE = 6; + + /** + * ISP name + * + * @var int + */ + const ISP = 7; + + /** + * Domain name + * + * @var int + */ + const DOMAIN_NAME = 8; + + /** + * Zip code + * + * @var int + */ + const ZIP_CODE = 9; + + /** + * Time zone + * + * @var int + */ + const TIME_ZONE = 10; + + /** + * Net speed + * + * @var int + */ + const NET_SPEED = 11; + + /** + * IDD code + * + * @var int + */ + const IDD_CODE = 12; + + /** + * Area code + * + * @var int + */ + const AREA_CODE = 13; + + /** + * Weather station code + * + * @var int + */ + const WEATHER_STATION_CODE = 14; + + /** + * Weather station name + * + * @var int + */ + const WEATHER_STATION_NAME = 15; + + /** + * Mobile Country Code + * + * @var int + */ + const MCC = 16; + + /** + * Mobile Network Code + * + * @var int + */ + const MNC = 17; + + /** + * Mobile carrier name + * + * @var int + */ + const MOBILE_CARRIER_NAME = 18; + + /** + * Elevation + * + * @var int + */ + const ELEVATION = 19; + + /** + * Usage type + * + * @var int + */ + const USAGE_TYPE = 20; + + /** + * Country name and code + * + * @var int + */ + const COUNTRY = 101; + + /** + * Latitude and Longitude + * + * @var int + */ + const COORDINATES = 102; + + /** + * IDD and area codes + * + * @var int + */ + const IDD_AREA = 103; + + /** + * Weather station name and code + * + * @var int + */ + const WEATHER_STATION = 104; + + /** + * MCC, MNC, and mobile carrier name + * + * @var int + */ + const MCC_MNC_MOBILE_CARRIER_NAME = 105; + + /** + * All fields at once + * + * @var int + */ + const ALL = 1001; + + /** + * Include the IP address of the looked up IP address + * + * @var int + */ + const IP_ADDRESS = 1002; + + /** + * Include the IP version of the looked up IP address + * + * @var int + */ + const IP_VERSION = 1003; + + /** + * Include the IP number of the looked up IP address + * + * @var int + */ + const IP_NUMBER = 1004; + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Exception code constants //////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Generic exception code + * + * @var int + */ + const EXCEPTION = 10000; + + /** + * No shmop extension found + * + * @var int + */ + const EXCEPTION_NO_SHMOP = 10001; + + /** + * Failed to open shmop memory segment for reading + * + * @var int + */ + const EXCEPTION_SHMOP_READING_FAILED = 10002; + + /** + * Failed to open shmop memory segment for writing + * + * @var int + */ + const EXCEPTION_SHMOP_WRITING_FAILED = 10003; + + /** + * Failed to create shmop memory segment + * + * @var int + */ + const EXCEPTION_SHMOP_CREATE_FAILED = 10004; + + /** + * The specified database file was not found + * + * @var int + */ + const EXCEPTION_DBFILE_NOT_FOUND = 10005; + + /** + * Not enough memory to load database file + * + * @var int + */ + const EXCEPTION_NO_MEMORY = 10006; + + /** + * No candidate databse files found + * + * @var int + */ + const EXCEPTION_NO_CANDIDATES = 10007; + + /** + * Failed to open database file + * + * @var int + */ + const EXCEPTION_FILE_OPEN_FAILED = 10008; + + /** + * Failed to determine the current path + * + * @var int + */ + const EXCEPTION_NO_PATH = 10009; + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Caching method constants //////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Directly read from the databse file + * + * @var int + */ + const FILE_IO = 100001; + + /** + * Read the whole database into a variable for caching + * + * @var int + */ + const MEMORY_CACHE = 100002; + + /** + * Use shared memory objects for caching + * + * @var int + */ + const SHARED_MEMORY = 100003; + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Shared memory constants ///////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Share memory segment's permissions (for creation) + * + * @var int + */ + const SHM_PERMS = 0600; + + /** + * Number of bytes to read/write at a time in order to load the shared memory cache (512k) + * + * @var int + */ + const SHM_CHUNK_SIZE = 524288; + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Static data ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Column offset mapping + * + * Each entry contains an array mapping databse version (0--23) to offset within a record. + * A value of 0 means the column is not present in the given database version. + * + * @access private + * @static + * @var array + */ + private static $columns = [ + self::COUNTRY_CODE => [8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8], + self::COUNTRY_NAME => [8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8], + self::REGION_NAME => [0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12], + self::CITY_NAME => [0, 0, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16], + self::LATITUDE => [0, 0, 0, 0, 20, 20, 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20], + self::LONGITUDE => [0, 0, 0, 0, 24, 24, 0, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24], + self::ISP => [0, 12, 0, 20, 0, 28, 20, 28, 0, 32, 0, 36, 0, 36, 0, 36, 0, 36, 28, 36, 0, 36, 28, 36], + self::DOMAIN_NAME => [0, 0, 0, 0, 0, 0, 24, 32, 0, 36, 0, 40, 0, 40, 0, 40, 0, 40, 32, 40, 0, 40, 32, 40], + self::ZIP_CODE => [0, 0, 0, 0, 0, 0, 0, 0, 28, 28, 28, 28, 0, 28, 28, 28, 0, 28, 0, 28, 28, 28, 0, 28], + self::TIME_ZONE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 32, 28, 32, 32, 32, 28, 32, 0, 32, 32, 32, 0, 32], + self::NET_SPEED => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 44, 0, 44, 32, 44, 0, 44, 0, 44, 0, 44], + self::IDD_CODE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 48, 0, 48, 0, 48, 36, 48, 0, 48], + self::AREA_CODE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 52, 0, 52, 0, 52, 40, 52, 0, 52], + self::WEATHER_STATION_CODE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 56, 0, 56, 0, 56, 0, 56], + self::WEATHER_STATION_NAME => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 60, 0, 60, 0, 60, 0, 60], + self::MCC => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 64, 0, 64, 36, 64], + self::MNC => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 68, 0, 68, 40, 68], + self::MOBILE_CARRIER_NAME => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 72, 0, 72, 44, 72], + self::ELEVATION => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 76, 0, 76], + self::USAGE_TYPE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 80], + ]; + + /** + * Column name mapping + * + * @access private + * @static + * @var array + */ + private static $names = [ + self::COUNTRY_CODE => 'countryCode', + self::COUNTRY_NAME => 'countryName', + self::REGION_NAME => 'regionName', + self::CITY_NAME => 'cityName', + self::LATITUDE => 'latitude', + self::LONGITUDE => 'longitude', + self::ISP => 'isp', + self::DOMAIN_NAME => 'domainName', + self::ZIP_CODE => 'zipCode', + self::TIME_ZONE => 'timeZone', + self::NET_SPEED => 'netSpeed', + self::IDD_CODE => 'iddCode', + self::AREA_CODE => 'areaCode', + self::WEATHER_STATION_CODE => 'weatherStationCode', + self::WEATHER_STATION_NAME => 'weatherStationName', + self::MCC => 'mcc', + self::MNC => 'mnc', + self::MOBILE_CARRIER_NAME => 'mobileCarrierName', + self::ELEVATION => 'elevation', + self::USAGE_TYPE => 'usageType', + self::IP_ADDRESS => 'ipAddress', + self::IP_VERSION => 'ipVersion', + self::IP_NUMBER => 'ipNumber', + ]; + + /** + * Database names, in order of preference for file lookup + * + * @var array + */ + private static $databases = [ + // IPv4 databases + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION-USAGETYPE', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ISP-DOMAIN-MOBILE-USAGETYPE', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-AREACODE-ELEVATION', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ISP-DOMAIN-MOBILE', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-NETSPEED-WEATHER', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-AREACODE', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-TIMEZONE-NETSPEED', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-ISP-DOMAIN', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ISP-DOMAIN', + 'IP-COUNTRY-REGION-CITY-ISP-DOMAIN', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ISP', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE', + 'IP-COUNTRY-REGION-CITY-ISP', + 'IP-COUNTRY-REGION-CITY', + 'IP-COUNTRY-ISP', + 'IP-COUNTRY', + // IPv6 databases + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION-USAGETYPE', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ISP-DOMAIN-MOBILE-USAGETYPE', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-AREACODE-ELEVATION', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ISP-DOMAIN-MOBILE', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-NETSPEED-WEATHER', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-AREACODE', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-TIMEZONE-NETSPEED', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-ISP-DOMAIN', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ISP-DOMAIN', + 'IPV6-COUNTRY-REGION-CITY-ISP-DOMAIN', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ISP', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE', + 'IPV6-COUNTRY-REGION-CITY-ISP', + 'IPV6-COUNTRY-REGION-CITY', + 'IPV6-COUNTRY-ISP', + 'IPV6-COUNTRY', + ]; + + /** + * Static memory buffer to use for MEMORY_CACHE mode, the keys will be BIN filenames and the values their contents + * + * @access private + * @static + * @var array + */ + private static $buffer = []; + + /** + * The machine's float size + * + * @access private + * @static + * @var int + */ + private static $floatSize = null; + + /** + * The configured memory limit + * + * @access private + * @static + * @var int + */ + private static $memoryLimit = null; + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Caching backend controls //////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Caching mode to use (one of FILE_IO, MEMORY_CACHE, or SHARED_MEMORY) + * + * @access private + * @var int + */ + private $mode; + + /** + * File pointer to use for FILE_IO mode, BIN filename for MEMORY_CACHE mode, or shared memory id to use for SHARED_MEMORY mode + * + * @access private + * @var resource|int|false + */ + private $resource = false; + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Database metadata /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Database's compilation date + * + * @access private + * @var int + */ + private $date; + + /** + * Database's type (0--23) + * + * @access private + * @var int + */ + private $type; + + /** + * Database's register width (as an array mapping 4 to IPv4 width, and 6 to IPv6 width) + * + * @access private + * @var array + */ + private $columnWidth = []; + + /** + * Database's pointer offset (as an array mapping 4 to IPv4 offset, and 6 to IPv6 offset) + * + * @access private + * @var array + */ + private $offset = []; + + /** + * Amount of IP address ranges the database contains (as an array mapping 4 to IPv4 count, and 6 to IPv6 count) + * + * @access private + * @var array + */ + private $ipCount = []; + + /** + * Offset withing the database where IP data begins (as an array mapping 4 to IPv4 base, and 6 to IPv6 base) + * + * @access private + * @var array + */ + private $ipBase = []; + + + //hjlim + private $indexBaseAddr = []; + private $year; + private $month; + private $day; + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Default fields ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Default fields to return during lookup + * + * @access private + * @var int|array + */ + private $defaultFields = self::ALL; + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Administrative public interface ///////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Constructor + * + * @access public + * @param string $file Filename of the BIN database to load + * @param int $mode Caching mode (one of FILE_IO, MEMORY_CACHE, or SHARED_MEMORY) + * @throws \Exception + */ + public function __construct($file = null, $mode = self::FILE_IO, $defaultFields = self::ALL) { + // find the referred file and its size + $rfile = self::findFile($file); + $size = filesize($rfile); + + // initialize caching backend + switch ($mode) { + case self::SHARED_MEMORY: + // verify the shmop extension is loaded + if (!extension_loaded('shmop')) { + throw new \Exception(__CLASS__ . ": Please make sure your PHP setup has the 'shmop' extension enabled.", self::EXCEPTION_NO_SHMOP); + } + + $limit = self::getMemoryLimit(); + if (false !== $limit && $size > $limit) { + throw new \Exception(__CLASS__ . ": Insufficient memory to load file '{$rfile}'.", self::EXCEPTION_NO_MEMORY); + } + + $this->mode = self::SHARED_MEMORY; + $shmKey = self::getShmKey($rfile); + + // try to open the shared memory segment + $this->resource = @shmop_open($shmKey, 'a', 0, 0); + if (false === $this->resource) { + // the segment did not exist, create it and load the database into it + $fp = fopen($rfile, 'rb'); + if (false === $fp) { + throw new \Exception(__CLASS__ . ": Unable to open file '{$rfile}'.", self::EXCEPTION_FILE_OPEN_FAILED); + } + + // try to open the memory segment for exclusive access + $shmId = @shmop_open($shmKey, 'n', self::SHM_PERMS, $size); + if (false === $shmId) { + throw new \Exception(__CLASS__ . ": Unable to create shared memory block '{$shmKey}'.", self::EXCEPTION_SHMOP_CREATE_FAILED); + } + + // load SHM_CHUNK_SIZE bytes at a time + $pointer = 0; + while ($pointer < $size) { + $buf = fread($fp, self::SHM_CHUNK_SIZE); + shmop_write($shmId, $buf, $pointer); + $pointer += self::SHM_CHUNK_SIZE; + } + shmop_close($shmId); + fclose($fp); + + // now open the memory segment for readonly access + $this->resource = @shmop_open($shmKey, 'a', 0, 0); + if (false === $this->resource) { + throw new \Exception(__CLASS__ . ": Unable to access shared memory block '{$shmKey}' for reading.", self::EXCEPTION_SHMOP_READING_FAILED); + } + } + break; + + case self::FILE_IO: + $this->mode = self::FILE_IO; + $this->resource = @fopen($rfile, 'rb'); + if (false === $this->resource) { + throw new \Exception(__CLASS__ . ": Unable to open file '{$rfile}'.", self::EXCEPTION_FILE_OPEN_FAILED); + } + break; + + case self::MEMORY_CACHE: + $this->mode = self::MEMORY_CACHE; + $this->resource = $rfile; + if (!array_key_exists($rfile, self::$buffer)) { + $limit = self::getMemoryLimit(); + if (false !== $limit && $size > $limit) { + throw new \Exception(__CLASS__ . ": Insufficient memory to load file '{$rfile}'.", self::EXCEPTION_NO_MEMORY); + } + + self::$buffer[$rfile] = @file_get_contents($rfile); + if (false === self::$buffer[$rfile]) { + throw new \Exception(__CLASS__ . ": Unable to open file '{$rfile}'.", self::EXCEPTION_FILE_OPEN_FAILED); + } + } + break; + + default: + } + + // determine the platform's float size + // + // NB: this should be a constant instead, and some unpack / typebanging magic + // should be used to accomodate different float sizes, but, as the libreary + // is written, this is the sanest thing to do anyway + // + if (null === self::$floatSize) { + self::$floatSize = strlen(pack('f', M_PI)); + } + + // set default fields to retrieve + $this->defaultFields = $defaultFields; + + // extract database metadata + $this->type = $this->readByte(1) - 1; + $this->columnWidth[4] = $this->readByte(2) * 4; + $this->columnWidth[6] = $this->columnWidth[4] + 12; + $this->offset[4] = -4; + $this->offset[6] = 8; + // + $this->year = 2000 + $this->readByte(3); + $this->month = $this->readByte(4); + $this->day = $this->readByte(5); + $this->date = date('Y-m-d', strtotime("{$this->year}-{$this->month}-{$this->day}")); + // + $this->ipCount[4] = $this->readWord(6); + $this->ipBase[4] = $this->readWord(10); //hjlim readword + $this->ipCount[6] = $this->readWord(14); + $this->ipBase[6] = $this->readWord(18); + $this->indexBaseAddr[4] = $this->readWord(22); //hjlim + $this->indexBaseAddr[6] = $this->readWord(26); //hjlim + } + + /** + * Destructor + * + * @access public + */ + public function __destruct() { + switch ($this->mode) { + case self::FILE_IO: + // free the file pointer + if (false !== $this->resource) { + fclose($this->resource); + $this->resource = false; + } + break; + case self::SHARED_MEMORY: + // detach from the memory segment + if (false !== $this->resource) { + shmop_close($this->resource); + $this->resource = false; + } + break; + } + } + + /** + * Tear down a shared memory segment created for the given file + * + * @access public + * @static + * @param string $file Filename of the BIN database whise segment must be deleted + * @throws \Exception + */ + public static function shmTeardown($file) { + // verify the shmop extension is loaded + if (!extension_loaded('shmop')) { + throw new \Exception(__CLASS__ . ": Please make sure your PHP setup has the 'shmop' extension enabled.", self::EXCEPTION_NO_SHMOP); + } + + // Get actual file path + $rfile = realpath($file); + + // If the file cannot be found, except away + if (false === $rfile) { + throw new \Exception(__CLASS__ . ": Database file '{$file}' does not seem to exist.", self::EXCEPTION_DBFILE_NOT_FOUND); + } + + $shmKey = self::getShmKey($rfile); + + // Try to open the memory segment for writing + $shmId = @shmop_open($shmKey, 'w', 0, 0); + if (false === $shmId) { + throw new \Exception(__CLASS__ . ": Unable to access shared memory block '{$shmKey}' for writing.", self::EXCEPTION_SHMOP_WRITING_FAILED); + } + + // Delete and close the descriptor + shmop_delete($shmId); + shmop_close($shmId); + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Static tools //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Get memory limit from the current PHP settings (return false if no memory limit set) + * + * @access private + * @static + * @return int|boolean + */ + private static function getMemoryLimit() { + // Get values if no cache + if (null === self::$memoryLimit) { + $limit = ini_get('memory_limit'); + + // Feal with defaults + if ('' === (string) $limit) { + $limit = '128M'; + } + + $value = (int) $limit; + + // Deal with "no-limit" + if ($value < 0) { + $value = false; + } else { + // Deal with shorthand bytes + switch (strtoupper(substr($limit, -1))) { + case 'G': $value *= 1024; + case 'M': $value *= 1024; + case 'K': $value *= 1024; + } + } + self::$memoryLimit = $value; + } + return self::$memoryLimit; + } + + /** + * Return the realpath of the given file or look for the first matching database option + * + * @param string $file File to try to find, or null to try the databases in turn on the current file's path + * @return string + * @throws \Exception + */ + private static function findFile($file = null) { + if (null !== $file) { + // Get actual file path + $rfile = realpath($file); + + // If the file cannot be found, except away + if (false === $rfile) { + throw new \Exception(__CLASS__ . ": Database file '{$file}' does not seem to exist.", self::EXCEPTION_DBFILE_NOT_FOUND); + } + + return $rfile; + } else { + // Try to get current path + $current = realpath(dirname(__FILE__)); + + if (false === $current) { + throw new \Exception(__CLASS__ . ": Cannot determine current path.", self::EXCEPTION_NO_PATH); + } + // Try each database in turn + foreach (self::$databases as $database) { + $rfile = realpath("{$current}/{$database}.BIN"); + if (false !== $rfile) { + return $rfile; + } + } + // No candidates found + throw new \Exception(__CLASS__ . ": No candidate database files found.", self::EXCEPTION_NO_CANDIDATES); + } + } + + /** + * Make the given number positive by wrapping it to 8 bit values + * + * @access private + * @static + * @param int $x Number to wrap + * @return int + */ + private static function wrap8($x) { + return $x + ($x < 0 ? 256 : 0); + } + + /** + * Make the given number positive by wrapping it to 32 bit values + * + * @access private + * @static + * @param int $x Number to wrap + * @return int + */ + private static function wrap32($x) { + return $x + ($x < 0 ? 4294967296 : 0); + } + + /** + * Generate a unique and repeatable shared memory key for each instance to use + * + * @access private + * @static + * @param string $filename Filename of the BIN file + * @return int + */ + private static function getShmKey($filename) { + // This will create a shared memory key that deterministically depends only on + // the current file's path and the BIN file's path + return (int) sprintf('%u', self::wrap32(crc32(__FILE__ . ':' . $filename))); + } + + /** + * Determine whether the given IP number of the given version lies between the given bounds + * + * This function will return 0 if the given ip number falls within the given bounds + * for the given version, -1 if it falls below, and 1 if it falls above. + * + * @access private + * @static + * @param int $version IP version to use (either 4 or 6) + * @param int|string $ip IP number to check (int for IPv4, string for IPv6) + * @param int|string $low Lower bound (int for IPv4, string for IPv6) + * @param int|string $high Uppoer bound (int for IPv4, string for IPv6) + * @return int + */ + private static function ipBetween($version, $ip, $low, $high) { + if (4 === $version) { + // Use normal PHP ints + if ($low <= $ip) { + if ($ip < $high) { + return 0; + } else { + return 1; + } + } else { + return -1; + } + } else { + // Use BCMath + if (bccomp($low, $ip, 0) <= 0) { + if (bccomp($ip, $high, 0) <= -1) { + return 0; + } else { + return 1; + } + } else { + return -1; + } + } + } + + /** + * Get the IP version and number of the given IP address + * + * This method will return an array, whose components will be: + * - first: 4 if the given IP address is an IPv4 one, 6 if it's an IPv6 one, + * or fase if it's neither. + * - second: the IP address' number if its version is 4, the number string if + * its version is 6, false otherwise. + * + * @access private + * @static + * @param string $ip IP address to extract the version and number for + * @return array + */ + private static function ipVersionAndNumber($ip) { + if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { + return [4, sprintf('%u', ip2long($ip))]; + } elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { + $result = 0; + + foreach (str_split(bin2hex(inet_pton($ip)), 8) as $word) { + $result = bcadd(bcmul($result, '4294967296', 0), self::wrap32(hexdec($word)), 0); + } + + return [6, $result]; + } else { + // Invalid IP address, return falses + return [false, false]; + } + } + + /** + * Return the decimal string representing the binary data given + * + * @access private + * @static + * @param string $data Binary data to parse + * @return string + */ + private static function bcBin2Dec($data) { + $parts = array( + unpack('V', substr($data, 12, 4)), + unpack('V', substr($data, 8, 4)), + unpack('V', substr($data, 4, 4)), + unpack('V', substr($data, 0, 4)), + ); + + foreach($parts as &$part) + if($part[1] < 0) + $part[1] += 4294967296; + + $result = bcadd(bcadd(bcmul($parts[0][1], bcpow(4294967296, 3)), bcmul($parts[1][1], bcpow(4294967296, 2))), bcadd(bcmul($parts[2][1], 4294967296), $parts[3][1])); + + return $result; + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Caching backend abstraction ///////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Low level read function to abstract away the caching mode being used + * + * @access private + * @param int $pos Position from where to start reading + * @param int $len Read this many bytes + * @return string + */ + private function read($pos, $len) { + switch ($this->mode) { + case self::SHARED_MEMORY: + return shmop_read($this->resource, $pos, $len); + + case self::MEMORY_CACHE: + return $data = substr(self::$buffer[$this->resource], $pos, $len); + + default: + fseek($this->resource, $pos, SEEK_SET); + return fread($this->resource, $len); + } + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Low-level read functions //////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Low level function to fetch a string from the caching backend + * + * @access private + * @param int $pos Position to read from + * @param int $additional Additional offset to apply + * @return string + */ + private function readString($pos, $additional = 0) { + // Get the actual pointer to the string's head + $spos = $this->readWord($pos) + $additional; + + // Read as much as the length (first "string" byte) indicates + return $this->read($spos + 1, $this->readByte($spos + 1)); + } + + /** + * Low level function to fetch a float from the caching backend + * + * @access private + * @param int $pos Position to read from + * @return float + */ + private function readFloat($pos) { + // Unpack a float's size worth of data + return unpack('f', $this->read($pos - 1, self::$floatSize))[1]; + } + + /** + * Low level function to fetch a quadword (128 bits) from the caching backend + * + * @access private + * @param int $pos Position to read from + * @return string + */ + private function readQuad($pos) { + // Use BCMath ints to get a quad's (128-bit) value + return self::bcBin2Dec($this->read($pos - 1, 16)); + } + + /** + * Low level function to fetch a word (32 bits) from the caching backend + * + * @access private + * @param int $pos Position to read from + * @return int + */ + private function readWord($pos) { + // Unpack a long's worth of data + return self::wrap32(unpack('V', $this->read($pos - 1, 4))[1]); + } + + /** + * Low level function to fetch a byte from the caching backend + * + * @access private + * @param int $pos Position to read from + * @return string + */ + private function readByte($pos) { + // Unpack a byte's worth of data + return self::wrap8(unpack('C', $this->read($pos - 1, 1))[1]); + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // High-level read functions /////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * High level function to fetch the country name and code + * + * @access private + * @param int|boolean $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * @return array + */ + private function readCountryNameAndCode($pointer) { + if (false === $pointer) { + // Deal with invalid IPs + $countryCode = self::INVALID_IP_ADDRESS; + $countryName = self::INVALID_IP_ADDRESS; + } elseif (0 === self::$columns[self::COUNTRY_CODE][$this->type]) { + // If the field is not suported, return accordingly + $countryCode = self::FIELD_NOT_SUPPORTED; + $countryName = self::FIELD_NOT_SUPPORTED; + } else { + // Read the country code and name (the name shares the country's pointer, + // but it must be artificially displaced 3 bytes ahead: 2 for the country code, one + // for the country name's length) + $countryCode = $this->readString($pointer + self::$columns[self::COUNTRY_CODE][$this->type]); + $countryName = $this->readString($pointer + self::$columns[self::COUNTRY_NAME][$this->type], 3); + } + + return [$countryName, $countryCode]; + } + + /** + * High level function to fetch the region name + * + * @access private + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * @return string + */ + private function readRegionName($pointer) { + if (false === $pointer) { + // Deal with invalid IPs + $regionName = self::INVALID_IP_ADDRESS; + } elseif (0 === self::$columns[self::REGION_NAME][$this->type]) { + // If the field is not suported, return accordingly + $regionName = self::FIELD_NOT_SUPPORTED; + } else { + // Read the region name + $regionName = $this->readString($pointer + self::$columns[self::REGION_NAME][$this->type]); + } + return $regionName; + } + + /** + * High level function to fetch the city name + * + * @access private + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * @return string + */ + private function readCityName($pointer) { + if (false === $pointer) { + // Deal with invalid IPs + $cityName = self::INVALID_IP_ADDRESS; + } elseif (0 === self::$columns[self::CITY_NAME][$this->type]) { + // If the field is not suported, return accordingly + $cityName = self::FIELD_NOT_SUPPORTED; + } else { + // Read the city name + $cityName = $this->readString($pointer + self::$columns[self::CITY_NAME][$this->type]); + } + return $cityName; + } + + /** + * High level function to fetch the latitude and longitude + * + * @access private + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * @return array + */ + private function readLatitudeAndLongitude($pointer) { + if (false === $pointer) { + // Deal with invalid IPs + $latitude = self::INVALID_IP_ADDRESS; + $longitude = self::INVALID_IP_ADDRESS; + } elseif (0 === self::$columns[self::LATITUDE][$this->type]) { + // If the field is not suported, return accordingly + $latitude = self::FIELD_NOT_SUPPORTED; + $longitude = self::FIELD_NOT_SUPPORTED; + } else { + // Read latitude and longitude + $latitude = $this->readFloat($pointer + self::$columns[self::LATITUDE][$this->type]); + $longitude = $this->readFloat($pointer + self::$columns[self::LONGITUDE][$this->type]); + } + return [$latitude, $longitude]; + } + + /** + * High level function to fetch the ISP name + * + * @access private + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * @return string + */ + private function readIsp($pointer) { + if (false === $pointer) { + // Deal with invalid IPs + $isp = self::INVALID_IP_ADDRESS; + } elseif (0 === self::$columns[self::ISP][$this->type]) { + // If the field is not suported, return accordingly + $isp = self::FIELD_NOT_SUPPORTED; + } else { + // Read isp name + $isp = $this->readString($pointer + self::$columns[self::ISP][$this->type]); + } + return $isp; + } + + /** + * High level function to fetch the domain name + * + * @access private + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * @return string + */ + private function readDomainName($pointer) { + if (false === $pointer) { + // Deal with invalid IPs + $domainName = self::INVALID_IP_ADDRESS; + } elseif (0 === self::$columns[self::DOMAIN_NAME][$this->type]) { + // If the field is not suported, return accordingly + $domainName = self::FIELD_NOT_SUPPORTED; + } else { + // Read the domain name + $domainName = $this->readString($pointer + self::$columns[self::DOMAIN_NAME][$this->type]); + } + return $domainName; + } + + /** + * High level function to fetch the zip code + * + * @access private + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * @return string + */ + private function readZipCode($pointer) { + if (false === $pointer) { + // Deal with invalid IPs + $zipCode = self::INVALID_IP_ADDRESS; + } elseif (0 === self::$columns[self::ZIP_CODE][$this->type]) { + // If the field is not suported, return accordingly + $zipCode = self::FIELD_NOT_SUPPORTED; + } else { + // Read the zip code + $zipCode = $this->readString($pointer + self::$columns[self::ZIP_CODE][$this->type]); + } + return $zipCode; + } + + /** + * High level function to fetch the time zone + * + * @access private + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * @return string + */ + private function readTimeZone($pointer) { + if (false === $pointer) { + // Deal with invalid IPs + $timeZone = self::INVALID_IP_ADDRESS; + } elseif (0 === self::$columns[self::TIME_ZONE][$this->type]) { + // If the field is not suported, return accordingly + $timeZone = self::FIELD_NOT_SUPPORTED; + } else { + // Read the time zone + $timeZone = $this->readString($pointer + self::$columns[self::TIME_ZONE][$this->type]); + } + return $timeZone; + } + + /** + * High level function to fetch the net speed + * + * @access private + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * @return string + */ + private function readNetSpeed($pointer) { + if (false === $pointer) { + // Deal with invalid IPs + $netSpeed = self::INVALID_IP_ADDRESS; + } elseif (0 === self::$columns[self::NET_SPEED][$this->type]) { + // If the field is not suported, return accordingly + $netSpeed = self::FIELD_NOT_SUPPORTED; + } else { + // Read the net speed + $netSpeed = $this->readString($pointer + self::$columns[self::NET_SPEED][$this->type]); + } + return $netSpeed; + } + + /** + * High level function to fetch the IDD and area codes + * + * @access private + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * @return array + */ + private function readIddAndAreaCodes($pointer) { + if (false === $pointer) { + // Deal with invalid IPs + $iddCode = self::INVALID_IP_ADDRESS; + $areaCode = self::INVALID_IP_ADDRESS; + } elseif (0 === self::$columns[self::IDD_CODE][$this->type]) { + // If the field is not suported, return accordingly + $iddCode = self::FIELD_NOT_SUPPORTED; + $areaCode = self::FIELD_NOT_SUPPORTED; + } else { + // Read IDD and area codes + $iddCode = $this->readString($pointer + self::$columns[self::IDD_CODE][$this->type]); + $areaCode = $this->readString($pointer + self::$columns[self::AREA_CODE][$this->type]); + } + return [$iddCode, $areaCode]; + } + + /** + * High level function to fetch the weather station name and code + * + * @access private + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * @return array + */ + private function readWeatherStationNameAndCode($pointer) { + if (false === $pointer) { + // Deal with invalid IPs + $weatherStationName = self::INVALID_IP_ADDRESS; + $weatherStationCode = self::INVALID_IP_ADDRESS; + } elseif (0 === self::$columns[self::WEATHER_STATION_NAME][$this->type]) { + // If the field is not suported, return accordingly + $weatherStationName = self::FIELD_NOT_SUPPORTED; + $weatherStationCode = self::FIELD_NOT_SUPPORTED; + } else { + // Read weather station name and code + $weatherStationName = $this->readString($pointer + self::$columns[self::WEATHER_STATION_NAME][$this->type]); + $weatherStationCode = $this->readString($pointer + self::$columns[self::WEATHER_STATION_CODE][$this->type]); + } + return [$weatherStationName, $weatherStationCode]; + } + + /** + * High level function to fetch the MCC, MNC, and mobile carrier name + * + * @access private + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * @return array + */ + private function readMccMncAndMobileCarrierName($pointer) { + if (false === $pointer) { + // Deal with invalid IPs + $mcc = self::INVALID_IP_ADDRESS; + $mnc = self::INVALID_IP_ADDRESS; + $mobileCarrierName = self::INVALID_IP_ADDRESS; + } elseif (0 === self::$columns[self::MCC][$this->type]) { + // If the field is not suported, return accordingly + $mcc = self::FIELD_NOT_SUPPORTED; + $mnc = self::FIELD_NOT_SUPPORTED; + $mobileCarrierName = self::FIELD_NOT_SUPPORTED; + } else { + // Read MCC, MNC, and mobile carrier name + $mcc = $this->readString($pointer + self::$columns[self::MCC][$this->type]); + $mnc = $this->readString($pointer + self::$columns[self::MNC][$this->type]); + $mobileCarrierName = $this->readString($pointer + self::$columns[self::MOBILE_CARRIER_NAME][$this->type]); + } + return [$mcc, $mnc, $mobileCarrierName]; + } + + /** + * High level function to fetch the elevation + * + * @access private + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * @return string + */ + private function readElevation($pointer) { + if (false === $pointer) { + // Deal with invalid IPs + $elevation = self::INVALID_IP_ADDRESS; + } elseif (0 === self::$columns[self::ELEVATION][$this->type]) { + // If the field is not suported, return accordingly + $elevation = self::FIELD_NOT_SUPPORTED; + } else { + // Read the elevation + $elevation = $this->readString($pointer + self::$columns[self::ELEVATION][$this->type]); + } + return $elevation; + } + + /** + * High level function to fetch the usage type + * + * @access private + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * @return string + */ + private function readUsageType($pointer) { + if (false === $pointer) { + // Deal with invalid IPs + $usageType = self::INVALID_IP_ADDRESS; + } elseif (0 === self::$columns[self::USAGE_TYPE][$this->type]) { + // If the field is not suported, return accordingly + $usageType = self::FIELD_NOT_SUPPORTED; + } else { + $usageType = $this->readString($pointer + self::$columns[self::USAGE_TYPE][$this->type]); + } + return $usageType; + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Binary search and support functions ///////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * High level fucntion to read an IP address of the given version + * + * @access private + * @param int $version IP version to read (either 4 or 6, returns false on anything else) + * @param int $pos Position to read from + * @return int|string|boolean + */ + private function readIp($version, $pos) { + if (4 === $version) { + // Read a standard PHP int + return self::wrap32($this->readWord($pos)); + } elseif (6 === $version) { + // Read as BCMath int (quad) + return $this->readQuad($pos); + } else { + // unrecognized + return false; + } + } + + /** + * Perform a binary search on the given IP number and return a pointer to its record + * + * @access private + * @param int $version IP version to use for searching + * @param int $ipNumber IP number to look for + * @return int|boolean + */ + private function binSearch($version, $ipNumber) { + if (false === $version) { + // unrecognized version + return false; + } + + // initialize fields + $base = $this->ipBase[$version]; + $offset = $this->offset[$version]; + $width = $this->columnWidth[$version]; + $high = $this->ipCount[$version]; + $low = 0; + + //hjlim + $indexBaseStart = $this->indexBaseAddr[$version]; + if ($indexBaseStart > 0){ + $indexPos = 0; + switch($version){ + case 4: + $ipNum1_2 = intval($ipNumber / 65536); + $indexPos = $indexBaseStart + ($ipNum1_2 << 3); + + break; + + case 6: + $ipNum1 = intval(bcdiv($ipNumber, bcpow('2', '112'))); + $indexPos = $indexBaseStart + ($ipNum1 << 3); + + break; + + default: + return false; + } + + $low = $this->readWord($indexPos); + $high = $this->readWord($indexPos + 4); + } + + // as long as we can narrow down the search... + while ($low <= $high) { + $mid = (int) ($low + (($high - $low) >> 1)); + + // Read IP ranges to get boundaries + $ip_from = $this->readIp($version, $base + $width * $mid); + $ip_to = $this->readIp($version, $base + $width * ($mid + 1)); + + // determine whether to return, repeat on the lower half, or repeat on the upper half + switch (self::ipBetween($version, $ipNumber, $ip_from, $ip_to)) { + case 0: + return $base + $offset + $mid * $width; + case -1: + $high = $mid - 1; + break; + case 1: + $low = $mid + 1; + break; + } + } + + // nothing found + return false; + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Public interface //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Get the database's compilation date as a string of the form 'YYYY-MM-DD' + * + * @access public + * @return string + */ + public function getDate() { + return $this->date; + } + + /** + * Get the database's type (1--24) + * + * @access public + * @return int + */ + public function getType() { + return $this->type + 1; + } + + /** + * Return this database's available fields + * + * @access public + * @param boolean $asNames Whether to return the mapped names intead of numbered constants + * @return array + */ + public function getFields($asNames = false) { + $result = array_keys(array_filter(self::$columns, function ($field) { + return 0 !== $field[$this->type]; + })); + if ($asNames) { + $return = []; + foreach ($result as $field) { + $return[] = self::$names[$field]; + } + return $return; + } else { + return $result; + } + } + + /** + * Return the version of module + */ + public function getModuleVersion() { + return self::VERSION; + } + + /** + * Return the version of module + */ + public function getDatabaseVersion() { + return $this->year . '.' . $this->month . '.' . $this->day; + } + + /** + * This function will look the given IP address up in the database and return the result(s) asked for + * + * If a single, SINGULAR, field is specified, only its mapped value is returned. + * If many fields are given (as an array) or a MULTIPLE field is specified, an + * array whith the returned singular field names as keys and their corresponding + * values is returned. + * + * @access public + * @param string $ip IP address to look up + * @param int|array $fields Field(s) to return + * @param boolean $asNamed Whether to return an associative array instead + * @return mixed|array|boolean + */ + public function lookup($ip, $fields = null, $asNamed = true) { + // extract IP version and number + list($ipVersion, $ipNumber) = self::ipVersionAndNumber($ip); + // perform the binary search proper (if the IP address was invalid, binSearch will return false) + $pointer = $this->binSearch($ipVersion, $ipNumber); + + // apply defaults if needed + if (null === $fields) { + $fields = $this->defaultFields; + } + + // turn fields into an array in case it wasn't already + $ifields = (array) $fields; + // add fields if needed + if (in_array(self::ALL, $ifields)) { + $ifields[] = self::REGION_NAME; + $ifields[] = self::CITY_NAME; + $ifields[] = self::ISP; + $ifields[] = self::DOMAIN_NAME; + $ifields[] = self::ZIP_CODE; + $ifields[] = self::TIME_ZONE; + $ifields[] = self::NET_SPEED; + $ifields[] = self::ELEVATION; + $ifields[] = self::USAGE_TYPE; + // + $ifields[] = self::COUNTRY; + $ifields[] = self::COORDINATES; + $ifields[] = self::IDD_AREA; + $ifields[] = self::WEATHER_STATION; + $ifields[] = self::MCC_MNC_MOBILE_CARRIER_NAME; + // + $ifields[] = self::IP_ADDRESS; + $ifields[] = self::IP_VERSION; + $ifields[] = self::IP_NUMBER; + } + // turn into a uniquely-valued array the fast way + // (see: http://php.net/manual/en/function.array-unique.php#77743) + $afields = array_keys(array_flip($ifields)); + // sorting them in reverse order warrants that by the time we get to + // SINGULAR fields, its MULTIPLE counterparts, if at all present, have + // already been retrieved + rsort($afields); + + // maintain a list of already retrieved fields to avoid doing it twice + $done = [ + self::COUNTRY_CODE => false, + self::COUNTRY_NAME => false, + self::REGION_NAME => false, + self::CITY_NAME => false, + self::LATITUDE => false, + self::LONGITUDE => false, + self::ISP => false, + self::DOMAIN_NAME => false, + self::ZIP_CODE => false, + self::TIME_ZONE => false, + self::NET_SPEED => false, + self::IDD_CODE => false, + self::AREA_CODE => false, + self::WEATHER_STATION_CODE => false, + self::WEATHER_STATION_NAME => false, + self::MCC => false, + self::MNC => false, + self::MOBILE_CARRIER_NAME => false, + self::ELEVATION => false, + self::USAGE_TYPE => false, + // + self::COUNTRY => false, + self::COORDINATES => false, + self::IDD_AREA => false, + self::WEATHER_STATION => false, + self::MCC_MNC_MOBILE_CARRIER_NAME => false, + // + self::IP_ADDRESS => false, + self::IP_VERSION => false, + self::IP_NUMBER => false, + ]; + // results are empty to begin with + $results = []; + + // treat each field in turn + foreach ($afields as $afield) { + switch ($afield) { + // purposefully ignore self::ALL, we already dealt with it + case self::ALL: break; + // + case self::COUNTRY: + if (!$done[self::COUNTRY]) { + list($results[self::COUNTRY_NAME], $results[self::COUNTRY_CODE]) = $this->readCountryNameAndCode($pointer); + $done[self::COUNTRY] = true; + $done[self::COUNTRY_CODE] = true; + $done[self::COUNTRY_NAME] = true; + } + break; + case self::COORDINATES: + if (!$done[self::COORDINATES]) { + list($results[self::LATITUDE], $results[self::LONGITUDE]) = $this->readLatitudeAndLongitude($pointer); + $done[self::COORDINATES] = true; + $done[self::LATITUDE] = true; + $done[self::LONGITUDE] = true; + } + break; + case self::IDD_AREA: + if (!$done[self::IDD_AREA]) { + list($results[self::IDD_CODE], $results[self::AREA_CODE]) = $this->readIddAndAreaCodes($pointer); + $done[self::IDD_AREA] = true; + $done[self::IDD_CODE] = true; + $done[self::AREA_CODE] = true; + } + break; + case self::WEATHER_STATION: + if (!$done[self::WEATHER_STATION]) { + list($results[self::WEATHER_STATION_NAME], $results[self::WEATHER_STATION_CODE]) = $this->readWeatherStationNameAndCode($pointer); + $done[self::WEATHER_STATION] = true; + $done[self::WEATHER_STATION_NAME] = true; + $done[self::WEATHER_STATION_CODE] = true; + } + break; + case self::MCC_MNC_MOBILE_CARRIER_NAME: + if (!$done[self::MCC_MNC_MOBILE_CARRIER_NAME]) { + list($results[self::MCC], $results[self::MNC], $results[self::MOBILE_CARRIER_NAME]) = $this->readMccMncAndMobileCarrierName($pointer); + $done[self::MCC_MNC_MOBILE_CARRIER_NAME] = true; + $done[self::MCC] = true; + $done[self::MNC] = true; + $done[self::MOBILE_CARRIER_NAME] = true; + } + break; + // + case self::COUNTRY_CODE: + if (!$done[self::COUNTRY_CODE]) { + $results[self::COUNTRY_CODE] = $this->readCountryNameAndCode($pointer)[1]; + $done[self::COUNTRY_CODE] = true; + } + break; + case self::COUNTRY_NAME: + if (!$done[self::COUNTRY_NAME]) { + $results[self::COUNTRY_NAME] = $this->readCountryNameAndCode($pointer)[0]; + $done[self::COUNTRY_NAME] = true; + } + break; + case self::REGION_NAME: + if (!$done[self::REGION_NAME]) { + $results[self::REGION_NAME] = $this->readRegionName($pointer); + $done[self::REGION_NAME] = true; + } + break; + case self::CITY_NAME: + if (!$done[self::CITY_NAME]) { + $results[self::CITY_NAME] = $this->readCityName($pointer); + $done[self::CITY_NAME] = true; + } + break; + case self::LATITUDE: + if (!$done[self::LATITUDE]) { + $results[self::LATITUDE] = $this->readLatitudeAndLongitude($pointer)[0]; + $done[self::LATITUDE] = true; + } + break; + case self::LONGITUDE: + if (!$done[self::LONGITUDE]) { + $results[self::LONGITUDE] = $this->readLatitudeAndLongitude($pointer)[1]; + $done[self::LONGITUDE] = true; + } + break; + case self::ISP: + if (!$done[self::ISP]) { + $results[self::ISP] = $this->readIsp($pointer); + $done[self::ISP] = true; + } + break; + case self::DOMAIN_NAME: + if (!$done[self::DOMAIN_NAME]) { + $results[self::DOMAIN_NAME] = $this->readDomainName($pointer); + $done[self::DOMAIN_NAME] = true; + } + break; + case self::ZIP_CODE: + if (!$done[self::ZIP_CODE]) { + $results[self::ZIP_CODE] = $this->readZipCode($pointer); + $done[self::ZIP_CODE] = true; + } + break; + case self::TIME_ZONE: + if (!$done[self::TIME_ZONE]) { + $results[self::TIME_ZONE] = $this->readTimeZone($pointer); + $done[self::TIME_ZONE] = true; + } + break; + case self::NET_SPEED: + if (!$done[self::NET_SPEED]) { + $results[self::NET_SPEED] = $this->readNetSpeed($pointer); + $done[self::NET_SPEED] = true; + } + break; + case self::IDD_CODE: + if (!$done[self::IDD_CODE]) { + $results[self::IDD_CODE] = $this->readIddAndAreaCodes($pointer)[0]; + $done[self::IDD_CODE] = true; + } + break; + case self::AREA_CODE: + if (!$done[self::AREA_CODE]) { + $results[self::AREA_CODE] = $this->readIddAndAreaCodes($pointer)[1]; + $done[self::AREA_CODE] = true; + } + break; + case self::WEATHER_STATION_CODE: + if (!$done[self::WEATHER_STATION_CODE]) { + $results[self::WEATHER_STATION_CODE] = $this->readWeatherStationNameAndCode($pointer)[1]; + $done[self::WEATHER_STATION_CODE] = true; + } + break; + case self::WEATHER_STATION_NAME: + if (!$done[self::WEATHER_STATION_NAME]) { + $results[self::WEATHER_STATION_NAME] = $this->readWeatherStationNameAndCode($pointer)[0]; + $done[self::WEATHER_STATION_NAME] = true; + } + break; + case self::MCC: + if (!$done[self::MCC]) { + $results[self::MCC] = $this->readMccMncAndMobileCarrierName($pointer)[0]; + $done[self::MCC] = true; + } + break; + case self::MNC: + if (!$done[self::MNC]) { + $results[self::MNC] = $this->readMccMncAndMobileCarrierName($pointer)[1]; + $done[self::MNC] = true; + } + break; + case self::MOBILE_CARRIER_NAME: + if (!$done[self::MOBILE_CARRIER_NAME]) { + $results[self::MOBILE_CARRIER_NAME] = $this->readMccMncAndMobileCarrierName($pointer)[2]; + $done[self::MOBILE_CARRIER_NAME] = true; + } + break; + case self::ELEVATION: + if (!$done[self::ELEVATION]) { + $results[self::ELEVATION] = $this->readElevation($pointer); + $done[self::ELEVATION] = true; + } + break; + case self::USAGE_TYPE: + if (!$done[self::USAGE_TYPE]) { + $results[self::USAGE_TYPE] = $this->readUsageType($pointer); + $done[self::USAGE_TYPE] = true; + } + break; + // + case self::IP_ADDRESS: + if (!$done[self::IP_ADDRESS]) { + $results[self::IP_ADDRESS] = $ip; + $done[self::IP_ADDRESS] = true; + } + break; + case self::IP_VERSION: + if (!$done[self::IP_VERSION]) { + $results[self::IP_VERSION] = $ipVersion; + $done[self::IP_VERSION] = true; + } + break; + case self::IP_NUMBER: + if (!$done[self::IP_NUMBER]) { + $results[self::IP_NUMBER] = $ipNumber; + $done[self::IP_NUMBER] = true; + } + break; + // + default: + $results[$afield] = self::FIELD_NOT_KNOWN; + } + } + + // If we were asked for an array, or we have multiple results to return... + if (is_array($fields) || count($results) > 1) { + // return array + if ($asNamed) { + // apply translations if needed + $return = []; + foreach ($results as $key => $val) { + if (array_key_exists($key, static::$names)) { + $return[static::$names[$key]] = $val; + } else { + $return[$key] = $val; + } + } + return $return; + } else { + return $results; + } + } else { + // return a single value + return array_values($results)[0]; + } + } + +} \ No newline at end of file diff --git a/application/libraries/Password.php b/application/libraries/Password.php new file mode 100644 index 0000000..a0c3154 --- /dev/null +++ b/application/libraries/Password.php @@ -0,0 +1,118 @@ +pbkdf2( + self::PBKDF2_HASH_ALGORITHM, + $password, + $salt, + self::PBKDF2_ITERATIONS, + self::PBKDF2_HASH_BYTE_SIZE, + true + )); + } + + function validate_password($password, $correct_hash) + { + $params = explode(":", $correct_hash); + if(count($params) < self::HASH_SECTIONS) + return false; + $pbkdf2 = base64_decode($params[self::HASH_PBKDF2_INDEX]); + return $this->slow_equals( + $pbkdf2, + $this->pbkdf2( + $params[self::HASH_ALGORITHM_INDEX], + $password, + $params[self::HASH_SALT_INDEX], + (int)$params[self::HASH_ITERATION_INDEX], + strlen($pbkdf2), + true + ) + ); + } + + // Compares two strings $a and $b in length-constant time. + function slow_equals($a, $b) + { + $diff = strlen($a) ^ strlen($b); + for($i = 0; $i < strlen($a) && $i < strlen($b); $i++) + { + $diff |= ord($a[$i]) ^ ord($b[$i]); + } + return $diff === 0; + } + + /* + * PBKDF2 key derivation function as defined by RSA's PKCS #5: https://www.ietf.org/rfc/rfc2898.txt + * $algorithm - The hash algorithm to use. Recommended: SHA256 + * $password - The password. + * $salt - A salt that is unique to the password. + * $count - Iteration count. Higher is better, but slower. Recommended: At least 1000. + * $key_length - The length of the derived key in bytes. + * $raw_output - If true, the key is returned in raw binary format. Hex encoded otherwise. + * Returns: A $key_length-byte key derived from the password and salt. + * + * Test vectors can be found here: https://www.ietf.org/rfc/rfc6070.txt + * + * This implementation of PBKDF2 was originally created by https://defuse.ca + * With improvements by http://www.variations-of-shadow.com + */ + function pbkdf2($algorithm, $password, $salt, $count, $key_length, $raw_output = false) + { + $algorithm = strtolower($algorithm); + if(!in_array($algorithm, hash_algos(), true)) + die('PBKDF2 ERROR: Invalid hash algorithm.'); + if($count <= 0 || $key_length <= 0) + die('PBKDF2 ERROR: Invalid parameters.'); + + $hash_length = strlen(hash($algorithm, "", true)); + $block_count = ceil($key_length / $hash_length); + + $output = ""; + for($i = 1; $i <= $block_count; $i++) { + // $i encoded as 4 bytes, big endian. + $last = $salt . pack("N", $i); + // first iteration + $last = $xorsum = hash_hmac($algorithm, $last, $password, true); + // perform the other $count - 1 iterations + for ($j = 1; $j < $count; $j++) { + $xorsum ^= ($last = hash_hmac($algorithm, $last, $password, true)); + } + $output .= $xorsum; + } + + if($raw_output) + return substr($output, 0, $key_length); + else + return bin2hex(substr($output, 0, $key_length)); + } + +} \ No newline at end of file diff --git a/application/libraries/REST_Controller.php b/application/libraries/REST_Controller.php new file mode 100644 index 0000000..94f378b --- /dev/null +++ b/application/libraries/REST_Controller.php @@ -0,0 +1,2136 @@ + 'application/json', + 'array' => 'application/json', + 'csv' => 'application/csv', + 'html' => 'text/html', + 'jsonp' => 'application/javascript', + 'php' => 'text/plain', + 'serialized' => 'application/vnd.php.serialized', + 'xml' => 'application/xml' + ]; + + /** + * Information about the current API user + * + * @var object + */ + protected $_apiuser; + + /** + * Enable XSS flag + * Determines whether the XSS filter is always active when + * GET, OPTIONS, HEAD, POST, PUT, DELETE and PATCH data is encountered. + * Set automatically based on config setting + * + * @var bool + */ + protected $_enable_xss = FALSE; + + /** + * HTTP status codes and their respective description + * Note: Only the widely used HTTP status codes are used + * + * @var array + * @link http://www.restapitutorial.com/httpstatuscodes.html + */ + protected $http_status_codes = [ + self::HTTP_OK => 'OK', + self::HTTP_CREATED => 'CREATED', + self::HTTP_NO_CONTENT => 'NO CONTENT', + self::HTTP_NOT_MODIFIED => 'NOT MODIFIED', + self::HTTP_BAD_REQUEST => 'BAD REQUEST', + self::HTTP_UNAUTHORIZED => 'UNAUTHORIZED', + self::HTTP_FORBIDDEN => 'FORBIDDEN', + self::HTTP_NOT_FOUND => 'NOT FOUND', + self::HTTP_METHOD_NOT_ALLOWED => 'METHOD NOT ALLOWED', + self::HTTP_NOT_ACCEPTABLE => 'NOT ACCEPTABLE', + self::HTTP_CONFLICT => 'CONFLICT', + self::HTTP_INTERNAL_SERVER_ERROR => 'INTERNAL SERVER ERROR', + self::HTTP_NOT_IMPLEMENTED => 'NOT IMPLEMENTED' + ]; + + /** + * Extend this function to apply additional checking early on in the process + * + * @access protected + * @return void + */ + protected function early_checks() + { + } + + /** + * Constructor for the REST API + * + * @access public + * @param string $config Configuration filename minus the file extension + * e.g: my_rest.php is passed as 'my_rest' + * @return void + */ + public function __construct($config = 'rest') + { + parent::__construct(); + + // Disable XML Entity (security vulnerability) + libxml_disable_entity_loader(TRUE); + + // Check to see if PHP is equal to or greater than 5.4.x + if (is_php('5.4') === FALSE) + { + // CodeIgniter 3 is recommended for v5.4 or above + throw new Exception('Using PHP v' . PHP_VERSION . ', though PHP v5.4 or greater is required'); + } + + // Check to see if this is CI 3.x + if (explode('.', CI_VERSION, 2)[0] < 3) + { + throw new Exception('REST Server requires CodeIgniter 3.x'); + } + + // Set the default value of global xss filtering. Same approach as CodeIgniter 3 + $this->_enable_xss = ($this->config->item('global_xss_filtering') === TRUE); + + // Don't try to parse template variables like {elapsed_time} and {memory_usage} + // when output is displayed for not damaging data accidentally + $this->output->parse_exec_vars = FALSE; + + // Start the timer for how long the request takes + $this->_start_rtime = microtime(TRUE); + + // Load the rest.php configuration file + $this->load->config($config); + + // At present the library is bundled with REST_Controller 2.5+, but will eventually be part of CodeIgniter (no citation) + $this->load->library('format'); + + // Get the language + $language = $this->config->item('rest_language'); + if ($language === NULL) + { + $language = 'english'; + } + + // Load the language file + $this->lang->load('rest_controller', $language); + + // Initialise the response, request and rest objects + $this->request = new stdClass(); + $this->response = new stdClass(); + $this->rest = new stdClass(); + + // Check to see if the current IP address is blacklisted + if ($this->config->item('rest_ip_blacklist_enabled') === TRUE) + { + $this->_check_blacklist_auth(); + } + + // Determine whether the connection is HTTPS + $this->request->ssl = is_https(); + + // How is this request being made? GET, POST, PATCH, DELETE, INSERT, PUT, HEAD or OPTIONS + $this->request->method = $this->_detect_method(); + + // Create an argument container if it doesn't exist e.g. _get_args + if (isset($this->{'_' . $this->request->method . '_args'}) === FALSE) + { + $this->{'_' . $this->request->method . '_args'} = []; + } + + // Set up the query parameters + $this->_parse_query(); + + // Set up the GET variables + $this->_get_args = array_merge($this->_get_args, $this->uri->ruri_to_assoc()); + + // Try to find a format for the request (means we have a request body) + $this->request->format = $this->_detect_input_format(); + + // Not all methods have a body attached with them + $this->request->body = NULL; + + $this->{'_parse_' . $this->request->method}(); + + // Now we know all about our request, let's try and parse the body if it exists + if ($this->request->format && $this->request->body) + { + $this->request->body = $this->format->factory($this->request->body, $this->request->format)->to_array(); + // Assign payload arguments to proper method container + $this->{'_' . $this->request->method . '_args'} = $this->request->body; + } + + // Merge both for one mega-args variable + $this->_args = array_merge( + $this->_get_args, + $this->_options_args, + $this->_patch_args, + $this->_head_args, + $this->_put_args, + $this->_post_args, + $this->_delete_args, + $this->{'_' . $this->request->method . '_args'} + ); + + // Which format should the data be returned in? + $this->response->format = $this->_detect_output_format(); + + // Which language should the data be returned in? + $this->response->lang = $this->_detect_lang(); + + // Extend this function to apply additional checking early on in the process + $this->early_checks(); + + // Load DB if its enabled + if ($this->config->item('rest_database_group') && ($this->config->item('rest_enable_keys') || $this->config->item('rest_enable_logging'))) + { + $this->rest->db = $this->load->database($this->config->item('rest_database_group'), TRUE); + } + + // Use whatever database is in use (isset returns FALSE) + elseif (property_exists($this, 'db')) + { + $this->rest->db = $this->db; + } + + // Check if there is a specific auth type for the current class/method + // _auth_override_check could exit so we need $this->rest->db initialized before + $this->auth_override = $this->_auth_override_check(); + + // Checking for keys? GET TO WorK! + // Skip keys test for $config['auth_override_class_method']['class'['method'] = 'none' + if ($this->config->item('rest_enable_keys') && $this->auth_override !== TRUE) + { + $this->_allow = $this->_detect_api_key(); + } + + // Only allow ajax requests + if ($this->input->is_ajax_request() === FALSE && $this->config->item('rest_ajax_only')) + { + // Display an error response + $this->response([ + $this->config->item('rest_status_field_name') => FALSE, + $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_ajax_only') + ], self::HTTP_NOT_ACCEPTABLE); + } + + // When there is no specific override for the current class/method, use the default auth value set in the config + if ($this->auth_override === FALSE && !($this->config->item('rest_enable_keys') && $this->_allow === TRUE)) + { + $rest_auth = strtolower($this->config->item('rest_auth')); + switch ($rest_auth) + { + case 'basic': + $this->_prepare_basic_auth(); + break; + case 'digest': + $this->_prepare_digest_auth(); + break; + case 'session': + $this->_check_php_session(); + break; + } + if ($this->config->item('rest_ip_whitelist_enabled') === TRUE) + { + $this->_check_whitelist_auth(); + } + } + } + + /** + * Deconstructor + * + * @author Chris Kacerguis + * @access public + * @return void + */ + public function __destruct() + { + // Get the current timestamp + $this->_end_rtime = microtime(TRUE); + + // Log the loading time to the log table + if ($this->config->item('rest_enable_logging') === TRUE) + { + $this->_log_access_time(); + } + } + + /** + * Requests are not made to methods directly, the request will be for + * an "object". This simply maps the object and method to the correct + * Controller method + * + * @access public + * @param string $object_called + * @param array $arguments The arguments passed to the controller method + */ + public function _remap($object_called, $arguments) + { + // Should we answer if not over SSL? + if ($this->config->item('force_https') && $this->request->ssl === FALSE) + { + $this->response([ + $this->config->item('rest_status_field_name') => FALSE, + $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_unsupported') + ], self::HTTP_FORBIDDEN); + } + + // Remove the supported format from the function name e.g. index.json => index + $object_called = preg_replace('/^(.*)\.(?:' . implode('|', array_keys($this->_supported_formats)) . ')$/', '$1', $object_called); + + $controller_method = $object_called . '_' . $this->request->method; + + // Do we want to log this method (if allowed by config)? + $log_method = !(isset($this->methods[$controller_method]['log']) && $this->methods[$controller_method]['log'] === FALSE); + + // Use keys for this method? + $use_key = !(isset($this->methods[$controller_method]['key']) && $this->methods[$controller_method]['key'] === FALSE); + + // They provided a key, but it wasn't valid, so get them out of here + if ($this->config->item('rest_enable_keys') && $use_key && $this->_allow === FALSE) + { + if ($this->config->item('rest_enable_logging') && $log_method) + { + $this->_log_request(); + } + + $this->response([ + $this->config->item('rest_status_field_name') => FALSE, + $this->config->item('rest_message_field_name') => sprintf($this->lang->line('text_rest_invalid_api_key'), $this->rest->key) + ], self::HTTP_FORBIDDEN); + } + + // Check to see if this key has access to the requested controller + if ($this->config->item('rest_enable_keys') && $use_key && empty($this->rest->key) === FALSE && $this->_check_access() === FALSE) + { + if ($this->config->item('rest_enable_logging') && $log_method) + { + $this->_log_request(); + } + + $this->response([ + $this->config->item('rest_status_field_name') => FALSE, + $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_api_key_unauthorized') + ], self::HTTP_UNAUTHORIZED); + } + + // Sure it exists, but can they do anything with it? + if (method_exists($this, $controller_method) === FALSE) + { + $this->response([ + $this->config->item('rest_status_field_name') => FALSE, + $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_unknown_method') + ], self::HTTP_NOT_FOUND); + } + + // Doing key related stuff? Can only do it if they have a key right? + if ($this->config->item('rest_enable_keys') && empty($this->rest->key) === FALSE) + { + // Check the limit + if ($this->config->item('rest_enable_limits') && $this->_check_limit($controller_method) === FALSE) + { + $response = [$this->config->item('rest_status_field_name') => FALSE, $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_api_key_time_limit')]; + $this->response($response, self::HTTP_UNAUTHORIZED); + } + + // If no level is set use 0, they probably aren't using permissions + $level = isset($this->methods[$controller_method]['level']) ? $this->methods[$controller_method]['level'] : 0; + + // If no level is set, or it is lower than/equal to the key's level + $authorized = $level <= $this->rest->level; + + // IM TELLIN! + if ($this->config->item('rest_enable_logging') && $log_method) + { + $this->_log_request($authorized); + } + + // They don't have good enough perms + $response = [$this->config->item('rest_status_field_name') => FALSE, $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_api_key_permissions')]; + $authorized || $this->response($response, self::HTTP_UNAUTHORIZED); + } + + // No key stuff, but record that stuff is happening + elseif ($this->config->item('rest_enable_logging') && $log_method) + { + $this->_log_request($authorized = TRUE); + } + + // Call the controller method and passed arguments + try + { + call_user_func_array([$this, $controller_method], $arguments); + } + catch (Exception $ex) + { + // If the method doesn't exist, then the error will be caught and an error response shown + $this->response([ + $this->config->item('rest_status_field_name') => FALSE, + $this->config->item('rest_message_field_name') => [ + 'classname' => get_class($ex), + 'message' => $ex->getMessage() + ] + ], self::HTTP_INTERNAL_SERVER_ERROR); + } + } + + /** + * Takes mixed data and optionally a status code, then creates the response + * + * @access public + * @param array|NULL $data Data to output to the user + * @param int|NULL $http_code HTTP status code + * @param bool $continue TRUE to flush the response to the client and continue + * running the script; otherwise, exit + */ + public function response($data = NULL, $http_code = NULL, $continue = FALSE) + { + // If the HTTP status is not NULL, then cast as an integer + if ($http_code !== NULL) + { + // So as to be safe later on in the process + $http_code = (int) $http_code; + } + + // Set the output as NULL by default + $output = NULL; + + // If data is NULL and no HTTP status code provided, then display, error and exit + if ($data === NULL && $http_code === NULL) + { + $http_code = self::HTTP_NOT_FOUND; + } + + // If data is not NULL and a HTTP status code provided, then continue + elseif ($data !== NULL) + { + // If the format method exists, call and return the output in that format + if (method_exists($this->format, 'to_' . $this->response->format)) + { + // Set the format header + $this->output->set_content_type($this->_supported_formats[$this->response->format], strtolower($this->config->item('charset'))); + $output = $this->format->factory($data)->{'to_' . $this->response->format}(); + + // An array must be parsed as a string, so as not to cause an array to string error + // Json is the most appropriate form for such a datatype + if ($this->response->format === 'array') + { + $output = $this->format->factory($output)->{'to_json'}(); + } + } + else + { + // If an array or object, then parse as a json, so as to be a 'string' + if (is_array($data) || is_object($data)) + { + $data = $this->format->factory($data)->{'to_json'}(); + } + + // Format is not supported, so output the raw data as a string + $output = $data; + } + } + + // If not greater than zero, then set the HTTP status code as 200 by default + // Though perhaps 500 should be set instead, for the developer not passing a + // correct HTTP status code + $http_code > 0 || $http_code = self::HTTP_OK; + + $this->output->set_status_header($http_code); + + // JC: Log response code only if rest logging enabled + if ($this->config->item('rest_enable_logging') === TRUE) + { + $this->_log_response_code($http_code); + } + + // Output the data + $this->output->set_output($output); + + if ($continue === FALSE) + { + // Display the data and exit execution + $this->output->_display(); + exit; + } + + // Otherwise dump the output automatically + } + + /** + * Takes mixed data and optionally a status code, then creates the response + * within the buffers of the Output class. The response is sent to the client + * lately by the framework, after the current controller's method termination. + * All the hooks after the controller's method termination are executable + * + * @access public + * @param array|NULL $data Data to output to the user + * @param int|NULL $http_code HTTP status code + */ + public function set_response($data = NULL, $http_code = NULL) + { + $this->response($data, $http_code, TRUE); + } + + /** + * Get the input format e.g. json or xml + * + * @access protected + * @return string|NULL Supported input format; otherwise, NULL + */ + protected function _detect_input_format() + { + // Get the CONTENT-TYPE value from the SERVER variable + $content_type = $this->input->server('CONTENT_TYPE'); + + if (empty($content_type) === FALSE) + { + // Check all formats against the HTTP_ACCEPT header + foreach ($this->_supported_formats as $key => $value) + { + // $key = format e.g. csv + // $value = mime type e.g. application/csv + + // If a semi-colon exists in the string, then explode by ; and get the value of where + // the current array pointer resides. This will generally be the first element of the array + $content_type = (strpos($content_type, ';') !== FALSE ? current(explode(';', $content_type)) : $content_type); + + // If both the mime types match, then return the format + if ($content_type === $value) + { + return $key; + } + } + } + + return NULL; + } + + /** + * Detect which format should be used to output the data + * + * @access protected + * @return mixed|NULL|string Output format + */ + protected function _detect_output_format() + { + // Concatenate formats to a regex pattern e.g. \.(csv|json|xml) + $pattern = '/\.(' . implode('|', array_keys($this->_supported_formats)) . ')($|\/)/'; + $matches = []; + + // Check if a file extension is used e.g. http://example.com/api/index.json?param1=param2 + if (preg_match($pattern, $this->uri->uri_string(), $matches)) + { + return $matches[1]; + } + + // Get the format parameter named as 'format' + if (isset($this->_get_args['format'])) + { + $format = strtolower($this->_get_args['format']); + + if (isset($this->_supported_formats[$format]) === TRUE) + { + return $format; + } + } + + // Get the HTTP_ACCEPT server variable + $http_accept = $this->input->server('HTTP_ACCEPT'); + + // Otherwise, check the HTTP_ACCEPT server variable + if ($this->config->item('rest_ignore_http_accept') === FALSE && $http_accept !== NULL) + { + // Check all formats against the HTTP_ACCEPT header + foreach (array_keys($this->_supported_formats) as $format) + { + // Has this format been requested? + if (strpos($http_accept, $format) !== FALSE) + { + if ($format !== 'html' && $format !== 'xml') + { + // If not HTML or XML assume it's correct + return $format; + } + elseif ($format === 'html' && strpos($http_accept, 'xml') === FALSE) + { + // HTML or XML have shown up as a match + // If it is truly HTML, it wont want any XML + return $format; + } + else if ($format === 'xml' && strpos($http_accept, 'html') === FALSE) + { + // If it is truly XML, it wont want any HTML + return $format; + } + } + } + } + + // Check if the controller has a default format + if (empty($this->rest_format) === FALSE) + { + return $this->rest_format; + } + + // Obtain the default format from the configuration + return $this->config->item('rest_default_format'); + } + + /** + * Get the HTTP request string e.g. get or post + * + * @access protected + * @return string|NULL Supported request method as a lowercase string; otherwise, NULL if not supported + */ + protected function _detect_method() + { + // Declare a variable to store the method + $method = NULL; + + // Determine whether the 'enable_emulate_request' setting is enabled + if ($this->config->item('enable_emulate_request') === TRUE) + { + $method = $this->input->post('_method'); + if ($method === NULL) + { + $method = $this->input->server('HTTP_X_HTTP_METHOD_OVERRIDE'); + } + + $method = strtolower($method); + } + + if (empty($method)) + { + // Get the request method as a lowercase string + $method = $this->input->method(); + } + + return in_array($method, $this->allowed_http_methods) && method_exists($this, '_parse_' . $method) ? $method : 'get'; + } + + /** + * See if the user has provided an API key + * + * @access protected + * @return bool + */ + protected function _detect_api_key() + { + // Get the api key name variable set in the rest config file + $api_key_variable = $this->config->item('rest_key_name'); + + // Work out the name of the SERVER entry based on config + $key_name = 'HTTP_' . strtoupper(str_replace('-', '_', $api_key_variable)); + + $this->rest->key = NULL; + $this->rest->level = NULL; + $this->rest->user_id = NULL; + $this->rest->ignore_limits = FALSE; + + // Find the key from server or arguments + if (($key = isset($this->_args[$api_key_variable]) ? $this->_args[$api_key_variable] : $this->input->server($key_name))) + { + if (!($row = $this->rest->db->where($this->config->item('rest_key_column'), $key)->get($this->config->item('rest_keys_table'))->row())) + { + return FALSE; + } + + $this->rest->key = $row->{$this->config->item('rest_key_column')}; + + isset($row->user_id) && $this->rest->user_id = $row->user_id; + isset($row->level) && $this->rest->level = $row->level; + isset($row->ignore_limits) && $this->rest->ignore_limits = $row->ignore_limits; + + $this->_apiuser = $row; + + /* + * If "is private key" is enabled, compare the ip address with the list + * of valid ip addresses stored in the database + */ + if (empty($row->is_private_key) === FALSE) + { + // Check for a list of valid ip addresses + if (isset($row->ip_addresses)) + { + // multiple ip addresses must be separated using a comma, explode and loop + $list_ip_addresses = explode(',', $row->ip_addresses); + $found_address = FALSE; + + foreach ($list_ip_addresses as $ip_address) + { + if ($this->input->ip_address() === trim($ip_address)) + { + // there is a match, set the the value to TRUE and break out of the loop + $found_address = TRUE; + break; + } + } + + return $found_address; + } + else + { + // There should be at least one IP address for this private key + return FALSE; + } + } + + return TRUE; + } + + // No key has been sent + return FALSE; + } + + /** + * Preferred return language + * + * @access protected + * @return string|NULL The language code + */ + protected function _detect_lang() + { + $lang = $this->input->server('HTTP_ACCEPT_LANGUAGE'); + if ($lang === NULL) + { + return NULL; + } + + // It appears more than one language has been sent using a comma delimiter + if (strpos($lang, ',') !== FALSE) + { + $langs = explode(',', $lang); + + $return_langs = []; + foreach ($langs as $lang) + { + // Remove weight and trim leading and trailing whitespace + list($lang) = explode(';', $lang); + $return_langs[] = trim($lang); + } + + return $return_langs; + } + + // Otherwise simply return as a string + return $lang; + } + + /** + * Add the request to the log table + * + * @access protected + * @param bool $authorized TRUE the user is authorized; otherwise, FALSE + * @return bool TRUE the data was inserted; otherwise, FALSE + */ + protected function _log_request($authorized = FALSE) + { + // Insert the request into the log table + $is_inserted = $this->rest->db + ->insert( + $this->config->item('rest_logs_table'), [ + 'uri' => $this->uri->uri_string(), + 'method' => $this->request->method, + 'params' => $this->_args ? ($this->config->item('rest_logs_json_params') === TRUE ? json_encode($this->_args) : serialize($this->_args)) : NULL, + 'api_key' => isset($this->rest->key) ? $this->rest->key : '', + 'ip_address' => $this->input->ip_address(), + 'time' => time(), + 'authorized' => $authorized + ]); + + // Get the last insert id to update at a later stage of the request + $this->_insert_id = $this->rest->db->insert_id(); + + return $is_inserted; + } + + /** + * Check if the requests to a controller method exceed a limit + * + * @access protected + * @param string $controller_method The method being called + * @return bool TRUE the call limit is below the threshold; otherwise, FALSE + */ + protected function _check_limit($controller_method) + { + // They are special, or it might not even have a limit + if (empty($this->rest->ignore_limits) === FALSE) + { + // Everything is fine + return TRUE; + } + + switch ($this->config->item('rest_limits_method')) + { + case 'API_KEY': + $limited_uri = 'api-key:' . (isset($this->rest->key) ? $this->rest->key : ''); + $limited_method_name = isset($this->rest->key) ? $this->rest->key : ''; + break; + + case 'METHOD_NAME': + $limited_uri = 'method-name:' . $controller_method; + $limited_method_name = $controller_method; + break; + + case 'ROUTED_URL': + default: + $limited_uri = $this->uri->ruri_string(); + if (strpos(strrev($limited_uri), strrev($this->response->format)) === 0) + { + $limited_uri = substr($limited_uri,0, -strlen($this->response->format) - 1); + } + $limited_uri = 'uri:' . $limited_uri . ':' . $this->request->method; // It's good to differentiate GET from PUT + $limited_method_name = $controller_method; + break; + } + + if (isset($this->methods[$limited_method_name]['limit']) === FALSE ) + { + // Everything is fine + return TRUE; + } + + // How many times can you get to this method in a defined time_limit (default: 1 hour)? + $limit = $this->methods[$limited_method_name]['limit']; + + $time_limit = (isset($this->methods[$limited_method_name]['time']) ? $this->methods[$limited_method_name]['time'] : 3600); // 3600 = 60 * 60 + + // Get data about a keys' usage and limit to one row + $result = $this->rest->db + ->where('uri', $limited_uri) + ->where('api_key', $this->rest->key) + ->get($this->config->item('rest_limits_table')) + ->row(); + + // No calls have been made for this key + if ($result === NULL) + { + // Create a new row for the following key + $this->rest->db->insert($this->config->item('rest_limits_table'), [ + 'uri' => $limited_uri, + 'api_key' => isset($this->rest->key) ? $this->rest->key : '', + 'count' => 1, + 'hour_started' => time() + ]); + } + + // Been a time limit (or by default an hour) since they called + elseif ($result->hour_started < (time() - $time_limit)) + { + // Reset the started period and count + $this->rest->db + ->where('uri', $limited_uri) + ->where('api_key', isset($this->rest->key) ? $this->rest->key : '') + ->set('hour_started', time()) + ->set('count', 1) + ->update($this->config->item('rest_limits_table')); + } + + // They have called within the hour, so lets update + else + { + // The limit has been exceeded + if ($result->count >= $limit) + { + return FALSE; + } + + // Increase the count by one + $this->rest->db + ->where('uri', $limited_uri) + ->where('api_key', $this->rest->key) + ->set('count', 'count + 1', FALSE) + ->update($this->config->item('rest_limits_table')); + } + + return TRUE; + } + + /** + * Check if there is a specific auth type set for the current class/method/HTTP-method being called + * + * @access protected + * @return bool + */ + protected function _auth_override_check() + { + // Assign the class/method auth type override array from the config + $auth_override_class_method = $this->config->item('auth_override_class_method'); + + // Check to see if the override array is even populated + if (!empty($auth_override_class_method)) + { + // check for wildcard flag for rules for classes + if (!empty($auth_override_class_method[$this->router->class]['*'])) // Check for class overrides + { + // None auth override found, prepare nothing but send back a TRUE override flag + if ($auth_override_class_method[$this->router->class]['*'] === 'none') + { + return TRUE; + } + + // Basic auth override found, prepare basic + if ($auth_override_class_method[$this->router->class]['*'] === 'basic') + { + $this->_prepare_basic_auth(); + + return TRUE; + } + + // Digest auth override found, prepare digest + if ($auth_override_class_method[$this->router->class]['*'] === 'digest') + { + $this->_prepare_digest_auth(); + + return TRUE; + } + + // Session auth override found, check session + if ($auth_override_class_method[$this->router->class]['*'] === 'session') + { + $this->_check_php_session(); + + return TRUE; + } + + // Whitelist auth override found, check client's ip against config whitelist + if ($auth_override_class_method[$this->router->class]['*'] === 'whitelist') + { + $this->_check_whitelist_auth(); + + return TRUE; + } + } + + // Check to see if there's an override value set for the current class/method being called + if (!empty($auth_override_class_method[$this->router->class][$this->router->method])) + { + // None auth override found, prepare nothing but send back a TRUE override flag + if ($auth_override_class_method[$this->router->class][$this->router->method] === 'none') + { + return TRUE; + } + + // Basic auth override found, prepare basic + if ($auth_override_class_method[$this->router->class][$this->router->method] === 'basic') + { + $this->_prepare_basic_auth(); + + return TRUE; + } + + // Digest auth override found, prepare digest + if ($auth_override_class_method[$this->router->class][$this->router->method] === 'digest') + { + $this->_prepare_digest_auth(); + + return TRUE; + } + + // Session auth override found, check session + if ($auth_override_class_method[$this->router->class][$this->router->method] === 'session') + { + $this->_check_php_session(); + + return TRUE; + } + + // Whitelist auth override found, check client's ip against config whitelist + if ($auth_override_class_method[$this->router->class][$this->router->method] === 'whitelist') + { + $this->_check_whitelist_auth(); + + return TRUE; + } + } + } + + // Assign the class/method/HTTP-method auth type override array from the config + $auth_override_class_method_http = $this->config->item('auth_override_class_method_http'); + + // Check to see if the override array is even populated + if (!empty($auth_override_class_method_http)) + { + // check for wildcard flag for rules for classes + if(!empty($auth_override_class_method_http[$this->router->class]['*'][$this->request->method])) + { + // None auth override found, prepare nothing but send back a TRUE override flag + if ($auth_override_class_method_http[$this->router->class]['*'][$this->request->method] === 'none') + { + return TRUE; + } + + // Basic auth override found, prepare basic + if ($auth_override_class_method_http[$this->router->class]['*'][$this->request->method] === 'basic') + { + $this->_prepare_basic_auth(); + + return TRUE; + } + + // Digest auth override found, prepare digest + if ($auth_override_class_method_http[$this->router->class]['*'][$this->request->method] === 'digest') + { + $this->_prepare_digest_auth(); + + return TRUE; + } + + // Session auth override found, check session + if ($auth_override_class_method_http[$this->router->class]['*'][$this->request->method] === 'session') + { + $this->_check_php_session(); + + return TRUE; + } + + // Whitelist auth override found, check client's ip against config whitelist + if ($auth_override_class_method_http[$this->router->class]['*'][$this->request->method] === 'whitelist') + { + $this->_check_whitelist_auth(); + + return TRUE; + } + } + + // Check to see if there's an override value set for the current class/method/HTTP-method being called + if(!empty($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method])) + { + // None auth override found, prepare nothing but send back a TRUE override flag + if ($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method] === 'none') + { + return TRUE; + } + + // Basic auth override found, prepare basic + if ($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method] === 'basic') + { + $this->_prepare_basic_auth(); + + return TRUE; + } + + // Digest auth override found, prepare digest + if ($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method] === 'digest') + { + $this->_prepare_digest_auth(); + + return TRUE; + } + + // Session auth override found, check session + if ($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method] === 'session') + { + $this->_check_php_session(); + + return TRUE; + } + + // Whitelist auth override found, check client's ip against config whitelist + if ($auth_override_class_method_http[$this->router->class][$this->router->method][$this->request->method] === 'whitelist') + { + $this->_check_whitelist_auth(); + + return TRUE; + } + } + } + return FALSE; + } + + /** + * Parse the GET request arguments + * + * @access protected + * @return void + */ + protected function _parse_get() + { + // Merge both the URI segments and query parameters + $this->_get_args = array_merge($this->_get_args, $this->_query_args); + } + + /** + * Parse the POST request arguments + * + * @access protected + * @return void + */ + protected function _parse_post() + { + $this->_post_args = $_POST; + + if ($this->request->format) + { + $this->request->body = $this->input->raw_input_stream; + } + } + + /** + * Parse the PUT request arguments + * + * @access protected + * @return void + */ + protected function _parse_put() + { + if ($this->request->format) + { + $this->request->body = $this->input->raw_input_stream; + } + else if ($this->input->method() === 'put') + { + // If no filetype is provided, then there are probably just arguments + $this->_put_args = $this->input->input_stream(); + } + } + + /** + * Parse the HEAD request arguments + * + * @access protected + * @return void + */ + protected function _parse_head() + { + // Parse the HEAD variables + parse_str(parse_url($this->input->server('REQUEST_URI'), PHP_URL_QUERY), $head); + + // Merge both the URI segments and HEAD params + $this->_head_args = array_merge($this->_head_args, $head); + } + + /** + * Parse the OPTIONS request arguments + * + * @access protected + * @return void + */ + protected function _parse_options() + { + // Parse the OPTIONS variables + parse_str(parse_url($this->input->server('REQUEST_URI'), PHP_URL_QUERY), $options); + + // Merge both the URI segments and OPTIONS params + $this->_options_args = array_merge($this->_options_args, $options); + } + + /** + * Parse the PATCH request arguments + * + * @access protected + * @return void + */ + protected function _parse_patch() + { + // It might be a HTTP body + if ($this->request->format) + { + $this->request->body = $this->input->raw_input_stream; + } + else if ($this->input->method() === 'patch') + { + // If no filetype is provided, then there are probably just arguments + $this->_patch_args = $this->input->input_stream(); + } + } + + /** + * Parse the DELETE request arguments + * + * @access protected + * @return void + */ + protected function _parse_delete() + { + // These should exist if a DELETE request + if ($this->input->method() === 'delete') + { + $this->_delete_args = $this->input->input_stream(); + } + } + + /** + * Parse the query parameters + * + * @access protected + * @return void + */ + protected function _parse_query() + { + // Declare a variable that will hold the REQUEST_URI + $request_uri = NULL; + + // If using the commandline version + if (is_cli()) + { + $args = $this->input->server('argv'); + unset($args[0]); + + // Combine the arguments using '/' as the delimiter + $request_uri = '/' . implode('/', $args) . '/'; + + // Set the following server variables (perhaps not required anymore?) + $_SERVER['REQUEST_URI'] = $request_uri; + $_SERVER['PATH_INFO'] = $request_uri; + $_SERVER['QUERY_STRING'] = $request_uri; + } + else + { + $request_uri = $this->input->server('REQUEST_URI'); + } + + // Parse the query parameters from the query string + parse_str(parse_url($request_uri, PHP_URL_QUERY), $this->_query_args); + } + + // INPUT FUNCTION -------------------------------------------------------------- + + /** + * Retrieve a value from a GET request + * + * @access public + * @param NULL $key Key to retrieve from the GET request + * If NULL an array of arguments is returned + * @param NULL $xss_clean Whether to apply XSS filtering + * @return array|string|NULL Value from the GET request; otherwise, NULL + */ + public function get($key = NULL, $xss_clean = NULL) + { + if ($key === NULL) + { + return $this->_get_args; + } + + return isset($this->_get_args[$key]) ? $this->_xss_clean($this->_get_args[$key], $xss_clean) : NULL; + } + + /** + * Retrieve a value from a OPTIONS request + * + * @access public + * @param NULL $key Key to retrieve from the OPTIONS request. + * If NULL an array of arguments is returned + * @param NULL $xss_clean Whether to apply XSS filtering + * @return array|string|NULL Value from the OPTIONS request; otherwise, NULL + */ + public function options($key = NULL, $xss_clean = NULL) + { + if ($key === NULL) + { + return $this->_options_args; + } + + return isset($this->_options_args[$key]) ? $this->_xss_clean($this->_options_args[$key], $xss_clean) : NULL; + } + + /** + * Retrieve a value from a HEAD request + * + * @access public + * @param NULL $key Key to retrieve from the HEAD request + * If NULL an array of arguments is returned + * @param NULL $xss_clean Whether to apply XSS filtering + * @return array|string|NULL Value from the HEAD request; otherwise, NULL + */ + public function head($key = NULL, $xss_clean = NULL) + { + if ($key === NULL) + { + return $this->_head_args; + } + + return isset($this->_head_args[$key]) ? $this->_xss_clean($this->_head_args[$key], $xss_clean) : NULL; + } + + /** + * Retrieve a value from a POST request + * + * @access public + * @param NULL $key Key to retrieve from the POST request + * If NULL an array of arguments is returned + * @param NULL $xss_clean Whether to apply XSS filtering + * @return array|string|NULL Value from the POST request; otherwise, NULL + */ + public function post($key = NULL, $xss_clean = NULL) + { + if ($key === NULL) + { + return $this->_post_args; + } + + return isset($this->_post_args[$key]) ? $this->_xss_clean($this->_post_args[$key], $xss_clean) : NULL; + } + + /** + * Retrieve a value from a PUT request + * + * @access public + * @param NULL $key Key to retrieve from the PUT request + * If NULL an array of arguments is returned + * @param NULL $xss_clean Whether to apply XSS filtering + * @return array|string|NULL Value from the PUT request; otherwise, NULL + */ + public function put($key = NULL, $xss_clean = NULL) + { + if ($key === NULL) + { + return $this->_put_args; + } + + return isset($this->_put_args[$key]) ? $this->_xss_clean($this->_put_args[$key], $xss_clean) : NULL; + } + + /** + * Retrieve a value from a DELETE request + * + * @access public + * @param NULL $key Key to retrieve from the DELETE request + * If NULL an array of arguments is returned + * @param NULL $xss_clean Whether to apply XSS filtering + * @return array|string|NULL Value from the DELETE request; otherwise, NULL + */ + public function delete($key = NULL, $xss_clean = NULL) + { + if ($key === NULL) + { + return $this->_delete_args; + } + + return isset($this->_delete_args[$key]) ? $this->_xss_clean($this->_delete_args[$key], $xss_clean) : NULL; + } + + /** + * Retrieve a value from a PATCH request + * + * @access public + * @param NULL $key Key to retrieve from the PATCH request + * If NULL an array of arguments is returned + * @param NULL $xss_clean Whether to apply XSS filtering + * @return array|string|NULL Value from the PATCH request; otherwise, NULL + */ + public function patch($key = NULL, $xss_clean = NULL) + { + if ($key === NULL) + { + return $this->_patch_args; + } + + return isset($this->_patch_args[$key]) ? $this->_xss_clean($this->_patch_args[$key], $xss_clean) : NULL; + } + + /** + * Retrieve a value from the query parameters + * + * @access public + * @param NULL $key Key to retrieve from the query parameters + * If NULL an array of arguments is returned + * @param NULL $xss_clean Whether to apply XSS filtering + * @return array|string|NULL Value from the query parameters; otherwise, NULL + */ + public function query($key = NULL, $xss_clean = NULL) + { + if ($key === NULL) + { + return $this->_query_args; + } + + return isset($this->_query_args[$key]) ? $this->_xss_clean($this->_query_args[$key], $xss_clean) : NULL; + } + + /** + * Sanitizes data so that Cross Site Scripting Hacks can be + * prevented + * + * @access protected + * @param string $value Input data + * @param bool $xss_clean Whether to apply XSS filtering + * @return string + */ + protected function _xss_clean($value, $xss_clean) + { + is_bool($xss_clean) || $xss_clean = $this->_enable_xss; + + return $xss_clean === TRUE ? $this->security->xss_clean($value) : $value; + } + + /** + * Retrieve the validation errors + * + * @access public + * @return array + */ + public function validation_errors() + { + $string = strip_tags($this->form_validation->error_string()); + + return explode(PHP_EOL, trim($string, PHP_EOL)); + } + + // SECURITY FUNCTIONS --------------------------------------------------------- + + /** + * Perform LDAP Authentication + * + * @access protected + * @param string $username The username to validate + * @param string $password The password to validate + * @return bool + */ + protected function _perform_ldap_auth($username = '', $password = NULL) + { + if (empty($username)) + { + log_message('debug', 'LDAP Auth: failure, empty username'); + return FALSE; + } + + log_message('debug', 'LDAP Auth: Loading configuration'); + + $this->config->load('ldap.php', TRUE); + + $ldap = [ + 'timeout' => $this->config->item('timeout', 'ldap'), + 'host' => $this->config->item('server', 'ldap'), + 'port' => $this->config->item('port', 'ldap'), + 'rdn' => $this->config->item('binduser', 'ldap'), + 'pass' => $this->config->item('bindpw', 'ldap'), + 'basedn' => $this->config->item('basedn', 'ldap'), + ]; + + log_message('debug', 'LDAP Auth: Connect to ' . (isset($ldaphost) ? $ldaphost : '[ldap not configured]')); + + // Connect to the ldap server + $ldapconn = ldap_connect($ldap['host'], $ldap['port']); + if ($ldapconn) + { + log_message('debug', 'Setting timeout to ' . $ldap['timeout'] . ' seconds'); + + ldap_set_option($ldapconn, LDAP_OPT_NETWORK_TIMEOUT, $ldap['timeout']); + + log_message('debug', 'LDAP Auth: Binding to ' . $ldap['host'] . ' with dn ' . $ldap['rdn']); + + // Binding to the ldap server + $ldapbind = ldap_bind($ldapconn, $ldap['rdn'], $ldap['pass']); + + // Verify the binding + if ($ldapbind === FALSE) + { + log_message('error', 'LDAP Auth: bind was unsuccessful'); + return FALSE; + } + + log_message('debug', 'LDAP Auth: bind successful'); + } + + // Search for user + if (($res_id = ldap_search($ldapconn, $ldap['basedn'], "uid=$username")) === FALSE) + { + log_message('error', 'LDAP Auth: User ' . $username . ' not found in search'); + return FALSE; + } + + if (ldap_count_entries($ldapconn, $res_id) !== 1) + { + log_message('error', 'LDAP Auth: Failure, username ' . $username . 'found more than once'); + return FALSE; + } + + if (($entry_id = ldap_first_entry($ldapconn, $res_id)) === FALSE) + { + log_message('error', 'LDAP Auth: Failure, entry of search result could not be fetched'); + return FALSE; + } + + if (($user_dn = ldap_get_dn($ldapconn, $entry_id)) === FALSE) + { + log_message('error', 'LDAP Auth: Failure, user-dn could not be fetched'); + return FALSE; + } + + // User found, could not authenticate as user + if (($link_id = ldap_bind($ldapconn, $user_dn, $password)) === FALSE) + { + log_message('error', 'LDAP Auth: Failure, username/password did not match: ' . $user_dn); + return FALSE; + } + + log_message('debug', 'LDAP Auth: Success ' . $user_dn . ' authenticated successfully'); + + $this->_user_ldap_dn = $user_dn; + + ldap_close($ldapconn); + + return TRUE; + } + + /** + * Perform Library Authentication - Override this function to change the way the library is called + * + * @access protected + * @param string $username The username to validate + * @param string $password The password to validate + * @return bool + */ + protected function _perform_library_auth($username = '', $password = NULL) + { + if (empty($username)) + { + log_message('error', 'Library Auth: Failure, empty username'); + return FALSE; + } + + $auth_library_class = strtolower($this->config->item('auth_library_class')); + $auth_library_function = strtolower($this->config->item('auth_library_function')); + + if (empty($auth_library_class)) + { + log_message('debug', 'Library Auth: Failure, empty auth_library_class'); + return FALSE; + } + + if (empty($auth_library_function)) + { + log_message('debug', 'Library Auth: Failure, empty auth_library_function'); + return FALSE; + } + + if (is_callable([$auth_library_class, $auth_library_function]) === FALSE) + { + $this->load->library($auth_library_class); + } + + return $this->{$auth_library_class}->$auth_library_function($username, $password); + } + + /** + * Check if the user is logged in + * + * @access protected + * @param string $username The user's name + * @param bool|string $password The user's password + * @return bool + */ + protected function _check_login($username = NULL, $password = FALSE) + { + if (empty($username)) + { + return FALSE; + } + + $auth_source = strtolower($this->config->item('auth_source')); + $rest_auth = strtolower($this->config->item('rest_auth')); + $valid_logins = $this->config->item('rest_valid_logins'); + + if (!$this->config->item('auth_source') && $rest_auth === 'digest') + { + // For digest we do not have a password passed as argument + return md5($username . ':' . $this->config->item('rest_realm') . ':' . (isset($valid_logins[$username]) ? $valid_logins[$username] : '')); + } + + if ($password === FALSE) + { + return FALSE; + } + + if ($auth_source === 'ldap') + { + log_message('debug', "Performing LDAP authentication for $username"); + + return $this->_perform_ldap_auth($username, $password); + } + + if ($auth_source === 'library') + { + log_message('debug', "Performing Library authentication for $username"); + + return $this->_perform_library_auth($username, $password); + } + + if (array_key_exists($username, $valid_logins) === FALSE) + { + return FALSE; + } + + if ($valid_logins[$username] !== $password) + { + return FALSE; + } + + return TRUE; + } + + /** + * Check to see if the user is logged in with a PHP session key + * + * @access protected + * @return void + */ + protected function _check_php_session() + { + // Get the auth_source config item + $key = $this->config->item('auth_source'); + + // If falsy, then the user isn't logged in + if (!$this->session->userdata($key)) + { + // Display an error response + $this->response([ + $this->config->item('rest_status_field_name') => FALSE, + $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_unauthorized') + ], self::HTTP_UNAUTHORIZED); + } + } + + /** + * Prepares for basic authentication + * + * @access protected + * @return void + */ + protected function _prepare_basic_auth() + { + // If whitelist is enabled it has the first chance to kick them out + if ($this->config->item('rest_ip_whitelist_enabled')) + { + $this->_check_whitelist_auth(); + } + + // Returns NULL if the SERVER variables PHP_AUTH_USER and HTTP_AUTHENTICATION don't exist + $username = $this->input->server('PHP_AUTH_USER'); + $http_auth = $this->input->server('HTTP_AUTHENTICATION'); + + $password = NULL; + if ($username !== NULL) + { + $password = $this->input->server('PHP_AUTH_PW'); + } + elseif ($http_auth !== NULL) + { + // If the authentication header is set as basic, then extract the username and password from + // HTTP_AUTHORIZATION e.g. my_username:my_password. This is passed in the .htaccess file + if (strpos(strtolower($http_auth), 'basic') === 0) + { + // Search online for HTTP_AUTHORIZATION workaround to explain what this is doing + list($username, $password) = explode(':', base64_decode(substr($this->input->server('HTTP_AUTHORIZATION'), 6))); + } + } + + // Check if the user is logged into the system + if ($this->_check_login($username, $password) === FALSE) + { + $this->_force_login(); + } + } + + /** + * Prepares for digest authentication + * + * @access protected + * @return void + */ + protected function _prepare_digest_auth() + { + // If whitelist is enabled it has the first chance to kick them out + if ($this->config->item('rest_ip_whitelist_enabled')) + { + $this->_check_whitelist_auth(); + } + + // We need to test which server authentication variable to use, + // because the PHP ISAPI module in IIS acts different from CGI + $digest_string = $this->input->server('PHP_AUTH_DIGEST'); + if ($digest_string === NULL) + { + $digest_string = $this->input->server('HTTP_AUTHORIZATION'); + } + + $unique_id = uniqid(); + + // The $_SESSION['error_prompted'] variable is used to ask the password + // again if none given or if the user enters wrong auth information + if (empty($digest_string)) + { + $this->_force_login($unique_id); + } + + // We need to retrieve authentication data from the $digest_string variable + $matches = []; + preg_match_all('@(username|nonce|uri|nc|cnonce|qop|response)=[\'"]?([^\'",]+)@', $digest_string, $matches); + $digest = (empty($matches[1]) || empty($matches[2])) ? [] : array_combine($matches[1], $matches[2]); + + // For digest authentication the library function should return already stored md5(username:restrealm:password) for that username @see rest.php::auth_library_function config + $username = $this->_check_login($digest['username'], TRUE); + if (array_key_exists('username', $digest) === FALSE || $username === FALSE) + { + $this->_force_login($unique_id); + } + + $md5 = md5(strtoupper($this->request->method) . ':' . $digest['uri']); + $valid_response = md5($username . ':' . $digest['nonce'] . ':' . $digest['nc'] . ':' . $digest['cnonce'] . ':' . $digest['qop'] . ':' . $md5); + + // Check if the string don't compare (case-insensitive) + if (strcasecmp($digest['response'], $valid_response) !== 0) + { + // Display an error response + $this->response([ + $this->config->item('rest_status_field_name') => FALSE, + $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_invalid_credentials') + ], self::HTTP_UNAUTHORIZED); + } + } + + /** + * Checks if the client's ip is in the 'rest_ip_blacklist' config and generates a 401 response + * + * @access protected + * @return void + */ + protected function _check_blacklist_auth() + { + // Match an ip address in a blacklist e.g. 127.0.0.0, 0.0.0.0 + $pattern = sprintf('/(?:,\s*|^)\Q%s\E(?=,\s*|$)/m', $this->input->ip_address()); + + // Returns 1, 0 or FALSE (on error only). Therefore implicitly convert 1 to TRUE + if (preg_match($pattern, $this->config->item('rest_ip_blacklist'))) + { + // Display an error response + $this->response([ + $this->config->item('rest_status_field_name') => FALSE, + $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_ip_denied') + ], self::HTTP_UNAUTHORIZED); + } + } + + /** + * Check if the client's ip is in the 'rest_ip_whitelist' config and generates a 401 response + * + * @access protected + * @return void + */ + protected function _check_whitelist_auth() + { + $whitelist = explode(',', $this->config->item('rest_ip_whitelist')); + + array_push($whitelist, '127.0.0.1', '0.0.0.0'); + + foreach ($whitelist as &$ip) + { + // As $ip is a reference, trim leading and trailing whitespace, then store the new value + // using the reference + $ip = trim($ip); + } + + if (in_array($this->input->ip_address(), $whitelist) === FALSE) + { + $this->response([ + $this->config->item('rest_status_field_name') => FALSE, + $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_ip_unauthorized') + ], self::HTTP_UNAUTHORIZED); + } + } + + /** + * Force logging in by setting the WWW-Authenticate header + * + * @access protected + * @param string $nonce A server-specified data string which should be uniquely generated + * each time + * @return void + */ + protected function _force_login($nonce = '') + { + $rest_auth = $this->config->item('rest_auth'); + $rest_realm = $this->config->item('rest_realm'); + if (strtolower($rest_auth) === 'basic') + { + // See http://tools.ietf.org/html/rfc2617#page-5 + header('WWW-Authenticate: Basic realm="' . $rest_realm . '"'); + } + elseif (strtolower($rest_auth) === 'digest') + { + // See http://tools.ietf.org/html/rfc2617#page-18 + header( + 'WWW-Authenticate: Digest realm="' . $rest_realm + . '", qop="auth", nonce="' . $nonce + . '", opaque="' . md5($rest_realm) . '"'); + } + + // Display an error response + $this->response([ + $this->config->item('rest_status_field_name') => FALSE, + $this->config->item('rest_message_field_name') => $this->lang->line('text_rest_unauthorized') + ], self::HTTP_UNAUTHORIZED); + } + + /** + * Updates the log table with the total access time + * + * @access protected + * @author Chris Kacerguis + * @return bool TRUE log table updated; otherwise, FALSE + */ + protected function _log_access_time() + { + $payload['rtime'] = $this->_end_rtime - $this->_start_rtime; + + return $this->rest->db->update( + $this->config->item('rest_logs_table'), $payload, [ + 'id' => $this->_insert_id + ]); + } + + /** + * Updates the log table with HTTP response code + * + * @access protected + * @author Justin Chen + * @param $http_code int HTTP status code + * @return bool TRUE log table updated; otherwise, FALSE + */ + protected function _log_response_code($http_code) + { + $payload['response_code'] = $http_code; + + return $this->rest->db->update( + $this->config->item('rest_logs_table'), $payload, [ + 'id' => $this->_insert_id + ]); + } + + /** + * Check to see if the API key has access to the controller and methods + * + * @access protected + * @return bool TRUE the API key has access; otherwise, FALSE + */ + protected function _check_access() + { + // If we don't want to check access, just return TRUE + if ($this->config->item('rest_enable_access') === FALSE) + { + return TRUE; + } + + // Fetch controller based on path and controller name + $controller = implode( + '/', [ + $this->router->directory, + $this->router->class + ]); + + // Remove any double slashes for safety + $controller = str_replace('//', '/', $controller); + + // Query the access table and get the number of results + return $this->rest->db + ->where('key', $this->rest->key) + ->where('controller', $controller) + ->get($this->config->item('rest_access_table')) + ->num_rows() > 0; + } + +} diff --git a/application/libraries/Tank_auth.php b/application/libraries/Tank_auth.php new file mode 100644 index 0000000..7a011c4 --- /dev/null +++ b/application/libraries/Tank_auth.php @@ -0,0 +1,645 @@ +ci =& get_instance(); + + $this->ci->load->config('tank_auth', TRUE); + + $this->ci->load->library('session'); + $this->ci->load->database(); + $this->ci->load->model('tank_auth/users'); + + // Try to autologin + $this->autologin(); + } + + /** + * Login user on the site. Return TRUE if login is successful + * (user exists and activated, password is correct), otherwise FALSE. + * + * @param string (username or email or both depending on settings in config file) + * @param string + * @param bool + * @return bool + */ + function login($login, $password, $remember, $login_by_username, $login_by_email) + { + if ((strlen($login) > 0) AND (strlen($password) > 0)) { + + // Which function to use to login (based on config) + if ($login_by_username AND $login_by_email) { + $get_user_func = 'get_user_by_login'; + } else if ($login_by_username) { + $get_user_func = 'get_user_by_username'; + } else { + $get_user_func = 'get_user_by_email'; + } + + if (!is_null($user = $this->ci->users->$get_user_func($login))) { // login ok + + // Does password match hash in database? + $hasher = new PasswordHash( + $this->ci->config->item('phpass_hash_strength', 'tank_auth'), + $this->ci->config->item('phpass_hash_portable', 'tank_auth')); + if ($hasher->CheckPassword($password, $user->password)) { // password ok + + if ($user->banned == 1) { // fail - banned + $this->error = array('banned' => $user->ban_reason); + + } else { + $this->ci->session->set_userdata(array( + 'user_id' => $user->id, + 'username' => $user->username, + 'status' => ($user->activated == 1) ? STATUS_ACTIVATED : STATUS_NOT_ACTIVATED, + )); + + if ($user->activated == 0) { // fail - not activated + $this->error = array('not_activated' => ''); + + } else { // success + if ($remember) { + $this->create_autologin($user->id); + } + + $this->clear_login_attempts($login); + + $this->ci->users->update_login_info( + $user->id, + $this->ci->config->item('login_record_ip', 'tank_auth'), + $this->ci->config->item('login_record_time', 'tank_auth')); + return TRUE; + } + } + } else { // fail - wrong password + $this->increase_login_attempt($login); + $this->error = array('password' => 'auth_incorrect_password'); + } + } else { // fail - wrong login + $this->increase_login_attempt($login); + $this->error = array('login' => 'auth_incorrect_login'); + } + } + return FALSE; + } + + /** + * Logout user from the site + * + * @return void + */ + function logout() + { + $this->delete_autologin(); + + // See http://codeigniter.com/forums/viewreply/662369/ as the reason for the next line + $this->ci->session->set_userdata(array('user_id' => '', 'username' => '', 'status' => '')); + + $this->ci->session->sess_destroy(); + } + + /** + * Check if user logged in. Also test if user is activated or not. + * + * @param bool + * @return bool + */ + function is_logged_in($activated = TRUE) + { + return $this->ci->session->userdata('status') === ($activated ? STATUS_ACTIVATED : STATUS_NOT_ACTIVATED); + } + + /** + * Get user_id + * + * @return string + */ + function get_user_id() + { + return $this->ci->session->userdata('user_id'); + } + + /** + * Get username + * + * @return string + */ + function get_username() + { + return $this->ci->session->userdata('username'); + } + + /** + * Create new user on the site and return some data about it: + * user_id, username, password, email, new_email_key (if any). + * + * @param string + * @param string + * @param string + * @param bool + * @return array + */ + function create_user($username, $email, $password, $email_activation) + { + if ((strlen($username) > 0) AND !$this->ci->users->is_username_available($username)) { + $this->error = array('username' => 'auth_username_in_use'); + + } elseif (!$this->ci->users->is_email_available($email)) { + $this->error = array('email' => 'auth_email_in_use'); + + } else { + // Hash password using phpass + $hasher = new PasswordHash( + $this->ci->config->item('phpass_hash_strength', 'tank_auth'), + $this->ci->config->item('phpass_hash_portable', 'tank_auth')); + $hashed_password = $hasher->HashPassword($password); + + $data = array( + 'username' => $username, + 'password' => $hashed_password, + 'email' => $email, + 'last_ip' => $this->ci->input->ip_address(), + ); + + if ($email_activation) { + $data['new_email_key'] = md5(rand().microtime()); + } + if (!is_null($res = $this->ci->users->create_user($data, !$email_activation))) { + $data['user_id'] = $res['user_id']; + $data['password'] = $password; + unset($data['last_ip']); + return $data; + } + } + return NULL; + } + + /** + * Check if username available for registering. + * Can be called for instant form validation. + * + * @param string + * @return bool + */ + function is_username_available($username) + { + return ((strlen($username) > 0) AND $this->ci->users->is_username_available($username)); + } + + /** + * Check if email available for registering. + * Can be called for instant form validation. + * + * @param string + * @return bool + */ + function is_email_available($email) + { + return ((strlen($email) > 0) AND $this->ci->users->is_email_available($email)); + } + + /** + * Change email for activation and return some data about user: + * user_id, username, email, new_email_key. + * Can be called for not activated users only. + * + * @param string + * @return array + */ + function change_email($email) + { + $user_id = $this->ci->session->userdata('user_id'); + + if (!is_null($user = $this->ci->users->get_user_by_id($user_id, FALSE))) { + + $data = array( + 'user_id' => $user_id, + 'username' => $user->username, + 'email' => $email, + ); + if (strtolower($user->email) == strtolower($email)) { // leave activation key as is + $data['new_email_key'] = $user->new_email_key; + return $data; + + } elseif ($this->ci->users->is_email_available($email)) { + $data['new_email_key'] = md5(rand().microtime()); + $this->ci->users->set_new_email($user_id, $email, $data['new_email_key'], FALSE); + return $data; + + } else { + $this->error = array('email' => 'auth_email_in_use'); + } + } + return NULL; + } + + /** + * Activate user using given key + * + * @param string + * @param string + * @param bool + * @return bool + */ + function activate_user($user_id, $activation_key, $activate_by_email = TRUE) + { + $this->ci->users->purge_na($this->ci->config->item('email_activation_expire', 'tank_auth')); + + if ((strlen($user_id) > 0) AND (strlen($activation_key) > 0)) { + return $this->ci->users->activate_user($user_id, $activation_key, $activate_by_email); + } + return FALSE; + } + + /** + * Set new password key for user and return some data about user: + * user_id, username, email, new_pass_key. + * The password key can be used to verify user when resetting his/her password. + * + * @param string + * @return array + */ + function forgot_password($login) + { + if (strlen($login) > 0) { + if (!is_null($user = $this->ci->users->get_user_by_login($login))) { + + $data = array( + 'user_id' => $user->id, + 'username' => $user->username, + 'email' => $user->email, + 'new_pass_key' => md5(rand().microtime()), + ); + + $this->ci->users->set_password_key($user->id, $data['new_pass_key']); + return $data; + + } else { + $this->error = array('login' => 'auth_incorrect_email_or_username'); + } + } + return NULL; + } + + /** + * Check if given password key is valid and user is authenticated. + * + * @param string + * @param string + * @return bool + */ + function can_reset_password($user_id, $new_pass_key) + { + if ((strlen($user_id) > 0) AND (strlen($new_pass_key) > 0)) { + return $this->ci->users->can_reset_password( + $user_id, + $new_pass_key, + $this->ci->config->item('forgot_password_expire', 'tank_auth')); + } + return FALSE; + } + + /** + * Replace user password (forgotten) with a new one (set by user) + * and return some data about it: user_id, username, new_password, email. + * + * @param string + * @param string + * @return bool + */ + function reset_password($user_id, $new_pass_key, $new_password) + { + if ((strlen($user_id) > 0) AND (strlen($new_pass_key) > 0) AND (strlen($new_password) > 0)) { + + if (!is_null($user = $this->ci->users->get_user_by_id($user_id, TRUE))) { + + // Hash password using phpass + $hasher = new PasswordHash( + $this->ci->config->item('phpass_hash_strength', 'tank_auth'), + $this->ci->config->item('phpass_hash_portable', 'tank_auth')); + $hashed_password = $hasher->HashPassword($new_password); + + if ($this->ci->users->reset_password( + $user_id, + $hashed_password, + $new_pass_key, + $this->ci->config->item('forgot_password_expire', 'tank_auth'))) { // success + + // Clear all user's autologins + $this->ci->load->model('tank_auth/user_autologin'); + $this->ci->user_autologin->clear($user->id); + + return array( + 'user_id' => $user_id, + 'username' => $user->username, + 'email' => $user->email, + 'new_password' => $new_password, + ); + } + } + } + return NULL; + } + + /** + * Change user password (only when user is logged in) + * + * @param string + * @param string + * @return bool + */ + function change_password($old_pass, $new_pass) + { + $user_id = $this->ci->session->userdata('user_id'); + + if (!is_null($user = $this->ci->users->get_user_by_id($user_id, TRUE))) { + + // Check if old password correct + $hasher = new PasswordHash( + $this->ci->config->item('phpass_hash_strength', 'tank_auth'), + $this->ci->config->item('phpass_hash_portable', 'tank_auth')); + if ($hasher->CheckPassword($old_pass, $user->password)) { // success + + // Hash new password using phpass + $hashed_password = $hasher->HashPassword($new_pass); + + // Replace old password with new one + $this->ci->users->change_password($user_id, $hashed_password); + return TRUE; + + } else { // fail + $this->error = array('old_password' => 'auth_incorrect_password'); + } + } + return FALSE; + } + + /** + * Change user email (only when user is logged in) and return some data about user: + * user_id, username, new_email, new_email_key. + * The new email cannot be used for login or notification before it is activated. + * + * @param string + * @param string + * @return array + */ + function set_new_email($new_email, $password) + { + $user_id = $this->ci->session->userdata('user_id'); + + if (!is_null($user = $this->ci->users->get_user_by_id($user_id, TRUE))) { + + // Check if password correct + $hasher = new PasswordHash( + $this->ci->config->item('phpass_hash_strength', 'tank_auth'), + $this->ci->config->item('phpass_hash_portable', 'tank_auth')); + if ($hasher->CheckPassword($password, $user->password)) { // success + + $data = array( + 'user_id' => $user_id, + 'username' => $user->username, + 'new_email' => $new_email, + ); + + if ($user->email == $new_email) { + $this->error = array('email' => 'auth_current_email'); + + } elseif ($user->new_email == $new_email) { // leave email key as is + $data['new_email_key'] = $user->new_email_key; + return $data; + + } elseif ($this->ci->users->is_email_available($new_email)) { + $data['new_email_key'] = md5(rand().microtime()); + $this->ci->users->set_new_email($user_id, $new_email, $data['new_email_key'], TRUE); + return $data; + + } else { + $this->error = array('email' => 'auth_email_in_use'); + } + } else { // fail + $this->error = array('password' => 'auth_incorrect_password'); + } + } + return NULL; + } + + /** + * Activate new email, if email activation key is valid. + * + * @param string + * @param string + * @return bool + */ + function activate_new_email($user_id, $new_email_key) + { + if ((strlen($user_id) > 0) AND (strlen($new_email_key) > 0)) { + return $this->ci->users->activate_new_email( + $user_id, + $new_email_key); + } + return FALSE; + } + + /** + * Delete user from the site (only when user is logged in) + * + * @param string + * @return bool + */ + function delete_user($password) + { + $user_id = $this->ci->session->userdata('user_id'); + + if (!is_null($user = $this->ci->users->get_user_by_id($user_id, TRUE))) { + + // Check if password correct + $hasher = new PasswordHash( + $this->ci->config->item('phpass_hash_strength', 'tank_auth'), + $this->ci->config->item('phpass_hash_portable', 'tank_auth')); + if ($hasher->CheckPassword($password, $user->password)) { // success + + $this->ci->users->delete_user($user_id); + $this->logout(); + return TRUE; + + } else { // fail + $this->error = array('password' => 'auth_incorrect_password'); + } + } + return FALSE; + } + + /** + * Get error message. + * Can be invoked after any failed operation such as login or register. + * + * @return string + */ + function get_error_message() + { + return $this->error; + } + + /** + * Save data for user's autologin + * + * @param int + * @return bool + */ + private function create_autologin($user_id) + { + $this->ci->load->helper('cookie'); + $key = substr(md5(uniqid(rand().get_cookie($this->ci->config->item('sess_cookie_name')))), 0, 16); + + $this->ci->load->model('tank_auth/user_autologin'); + $this->ci->user_autologin->purge($user_id); + + if ($this->ci->user_autologin->set($user_id, md5($key))) { + set_cookie(array( + 'name' => $this->ci->config->item('autologin_cookie_name', 'tank_auth'), + 'value' => serialize(array('user_id' => $user_id, 'key' => $key)), + 'expire' => $this->ci->config->item('autologin_cookie_life', 'tank_auth'), + )); + return TRUE; + } + return FALSE; + } + + /** + * Clear user's autologin data + * + * @return void + */ + private function delete_autologin() + { + $this->ci->load->helper('cookie'); + if ($cookie = get_cookie($this->ci->config->item('autologin_cookie_name', 'tank_auth'), TRUE)) { + + $data = unserialize($cookie); + + $this->ci->load->model('tank_auth/user_autologin'); + $this->ci->user_autologin->delete($data['user_id'], md5($data['key'])); + + delete_cookie($this->ci->config->item('autologin_cookie_name', 'tank_auth')); + } + } + + /** + * Login user automatically if he/she provides correct autologin verification + * + * @return void + */ + private function autologin() + { + if (!$this->is_logged_in() AND !$this->is_logged_in(FALSE)) { // not logged in (as any user) + + $this->ci->load->helper('cookie'); + if ($cookie = get_cookie($this->ci->config->item('autologin_cookie_name', 'tank_auth'), TRUE)) { + + $data = unserialize($cookie); + + if (isset($data['key']) AND isset($data['user_id'])) { + + $this->ci->load->model('tank_auth/user_autologin'); + if (!is_null($user = $this->ci->user_autologin->get($data['user_id'], md5($data['key'])))) { + + // Login user + $this->ci->session->set_userdata(array( + 'user_id' => $user->id, + 'username' => $user->username, + 'status' => STATUS_ACTIVATED, + )); + + // Renew users cookie to prevent it from expiring + set_cookie(array( + 'name' => $this->ci->config->item('autologin_cookie_name', 'tank_auth'), + 'value' => $cookie, + 'expire' => $this->ci->config->item('autologin_cookie_life', 'tank_auth'), + )); + + $this->ci->users->update_login_info( + $user->id, + $this->ci->config->item('login_record_ip', 'tank_auth'), + $this->ci->config->item('login_record_time', 'tank_auth')); + return TRUE; + } + } + } + } + return FALSE; + } + + /** + * Check if login attempts exceeded max login attempts (specified in config) + * + * @param string + * @return bool + */ + function is_max_login_attempts_exceeded($login) + { + if ($this->ci->config->item('login_count_attempts', 'tank_auth')) { + $this->ci->load->model('tank_auth/login_attempts'); + return $this->ci->login_attempts->get_attempts_num($this->ci->input->ip_address(), $login) + >= $this->ci->config->item('login_max_attempts', 'tank_auth'); + } + return FALSE; + } + + /** + * Increase number of attempts for given IP-address and login + * (if attempts to login is being counted) + * + * @param string + * @return void + */ + private function increase_login_attempt($login) + { + if ($this->ci->config->item('login_count_attempts', 'tank_auth')) { + if (!$this->is_max_login_attempts_exceeded($login)) { + $this->ci->load->model('tank_auth/login_attempts'); + $this->ci->login_attempts->increase_attempt($this->ci->input->ip_address(), $login); + } + } + } + + /** + * Clear all attempt records for given IP-address and login + * (if attempts to login is being counted) + * + * @param string + * @return void + */ + private function clear_login_attempts($login) + { + if ($this->ci->config->item('login_count_attempts', 'tank_auth')) { + $this->ci->load->model('tank_auth/login_attempts'); + $this->ci->login_attempts->clear_attempts( + $this->ci->input->ip_address(), + $login, + $this->ci->config->item('login_attempt_expire', 'tank_auth')); + } + } +} + +/* End of file Tank_auth.php */ +/* Location: ./application/libraries/Tank_auth.php */ \ No newline at end of file diff --git a/application/libraries/ci_chat.php b/application/libraries/ci_chat.php new file mode 100644 index 0000000..3b6ea98 --- /dev/null +++ b/application/libraries/ci_chat.php @@ -0,0 +1,281 @@ +ci =& get_instance(); + $this->ci->load->database(); + //$this->ci->load->model('chat_model'); + if (!isset($_SESSION['chatHistory'])) { + $_SESSION['chatHistory'] = array(); + } + if (!isset($_SESSION['openChatBoxes'])) { + $_SESSION['openChatBoxes'] = array(); + } + + if( isset( $_GET['action'] ) ){ + if ($_GET['action'] == "chatheartbeat") { + $this->chatHeartbeat(); + } + if ($_GET['action'] == "sendchat") { + $this->sendChat(); + } + if ($_GET['action'] == "closechat") { + $this->closeChat(); + } + if ($_GET['action'] == "startchatsession") { + $this->startChatSession(); + } + if ($_GET['action'] == "chathistory") { + $this->chatHistory(); + } + } + } + /* + ------------ + */ + function startChatSession() { + $items = ''; + if (!empty($_SESSION['openChatBoxes'])) { + foreach ($_SESSION['openChatBoxes'] as $chatbox => $void) { + $items .= $this->chatBoxSession($chatbox); + } + } + if ($items != '') { + $items = substr($items, 0, -1); + } + header('Content-type: application/json'); + ?> + { + "username": "", + "from_username": "", + "items": [ + + ] + } + ",$text); + return $text; + } + /* + -------- + */ + + function sendChat() { + $from = $_SESSION['username']; + $from_name = $_SESSION['chatusername']; + $to = $_POST['to']; + $to_name = $_POST['to_name']; + $message = $_POST['message']; + $_SESSION['openChatBoxes'][$_POST['to']] = date('Y-m-d H:i:s', time()); + + $messagesan = $this->sanitize($message); + + if (!isset($_SESSION['chatHistory'][$_POST['to']])) { + $_SESSION['chatHistory'][$_POST['to']] = ''; + } + $d['s'] = '1'; + $d['fname'] = "$to_name"; + $d['f'] = "$to"; + $d['m'] = "$messagesan"; + + $_SESSION['chatHistory'][$_POST['to']] .= json_encode( $d ).",\r\n"; + /*$_SESSION['chatHistory'][$_POST['to']] .= << $from , + 'from_name' => $from_name , + 'to_name' => $to_name , + 'to' => $to , + 'message' => $message, + ); + $this->ci->db->set('sent', 'NOW()', FALSE); + $this->ci->db->insert('chat', $data); + echo "1"; + exit(0); + } + + + function chatHistory(){ + $this->ci->db->select('*'); + $this->ci->db->where("(`from` = $_SESSION[username] AND `to` = ".$this->ci->input->get('to')." )"); + $this->ci->db->or_where("(`to` = $_SESSION[username] AND `from` = ".$this->ci->input->get('to')." )"); + $this->ci->db->order_by('id','ASC' ); + $query = $this->ci->db->get('chat'); + $items = ''; + $chatBoxes = array(); + foreach ($query->result_array() as $chat) { + # code... + $chat['message'] = $this->sanitize($chat['message']); + + $d = array(); + $d['s'] = '0'; + $d['f'] = "$chat[from]"; + $d['fname'] = "$chat[from_name]"; + $d['m'] = "$chat[message]"; + $items.=json_encode( $d ).','; + } + header('Content-type: application/json'); + ?> +{ + "items": [ + + ] +} + + ci->db->select('*'); + $this->ci->db->where('to', $_SESSION['username'] ); + $this->ci->db->where('recd',0 ); + $this->ci->db->order_by('id','ASC' ); + $query = $this->ci->db->get('chat'); + $items = ''; + $chatBoxes = array(); + foreach ($query->result_array() as $chat) { + # code... + if (!isset($_SESSION['openChatBoxes'][$chat['from']]) && isset($_SESSION['chatHistory'][$chat['from']])) { + $items = $_SESSION['chatHistory'][$chat['from']]; + } + + $chat['message'] = $this->sanitize($chat['message']); + + $d = array(); + $d['s'] = '0'; + $d['f'] = "$chat[from]"; + $d['fname'] = "$chat[from_name]"; + $d['m'] = "$chat[message]"; + /*$items .= << $time) { + if (!isset($_SESSION['tsChatBoxes'][$chatbox])) { + $now = time()-strtotime($time); + $time = date('g:iA M dS', strtotime($time)); + + $message = "Sent at $time"; + if ($now > 180) { + + $d = array(); + $d['s'] = '2'; + $d['f'] = "$chatbox"; + $d['m'] = "$message"; + + $items.= json_encode( $d ).",\r\n"; + // $items .= << + { + "items": [ + + ] + } + + \ No newline at end of file diff --git a/application/libraries/ip2location/IP2LOCATION-DB.BIN b/application/libraries/ip2location/IP2LOCATION-DB.BIN new file mode 100644 index 0000000..9bbd4b7 Binary files /dev/null and b/application/libraries/ip2location/IP2LOCATION-DB.BIN differ diff --git a/application/libraries/ip2location/IP2Location.php b/application/libraries/ip2location/IP2Location.php new file mode 100644 index 0000000..6c34fef --- /dev/null +++ b/application/libraries/ip2location/IP2Location.php @@ -0,0 +1,1904 @@ +. + * + */ + +namespace IP2Location; + +/** + * IP2Location database class + * + */ +class Database { + + /** + * Current module's version + * + * @var string + */ + const VERSION = '8.0.2'; + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Error field constants /////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Unsupported field message + * + * @var string + */ + const FIELD_NOT_SUPPORTED = 'This parameter is unavailable in selected .BIN data file. Please upgrade.'; + + /** + * Unknown field message + * + * @var string + */ + const FIELD_NOT_KNOWN = 'This parameter is inexistent. Please verify.'; + + /** + * Invalid IP address message + * + * @var string + */ + const INVALID_IP_ADDRESS = 'Invalid IP address.'; + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Field selection constants /////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Country code (ISO 3166-1 Alpha 2) + * + * @var int + */ + const COUNTRY_CODE = 1; + + /** + * Country name + * + * @var int + */ + const COUNTRY_NAME = 2; + + /** + * Region name + * + * @var int + */ + const REGION_NAME = 3; + + /** + * City name + * + * @var int + */ + const CITY_NAME = 4; + + /** + * Latitude + * + * @var int + */ + const LATITUDE = 5; + + /** + * Longitude + * + * @var int + */ + const LONGITUDE = 6; + + /** + * ISP name + * + * @var int + */ + const ISP = 7; + + /** + * Domain name + * + * @var int + */ + const DOMAIN_NAME = 8; + + /** + * Zip code + * + * @var int + */ + const ZIP_CODE = 9; + + /** + * Time zone + * + * @var int + */ + const TIME_ZONE = 10; + + /** + * Net speed + * + * @var int + */ + const NET_SPEED = 11; + + /** + * IDD code + * + * @var int + */ + const IDD_CODE = 12; + + /** + * Area code + * + * @var int + */ + const AREA_CODE = 13; + + /** + * Weather station code + * + * @var int + */ + const WEATHER_STATION_CODE = 14; + + /** + * Weather station name + * + * @var int + */ + const WEATHER_STATION_NAME = 15; + + /** + * Mobile Country Code + * + * @var int + */ + const MCC = 16; + + /** + * Mobile Network Code + * + * @var int + */ + const MNC = 17; + + /** + * Mobile carrier name + * + * @var int + */ + const MOBILE_CARRIER_NAME = 18; + + /** + * Elevation + * + * @var int + */ + const ELEVATION = 19; + + /** + * Usage type + * + * @var int + */ + const USAGE_TYPE = 20; + + /** + * Country name and code + * + * @var int + */ + const COUNTRY = 101; + + /** + * Latitude and Longitude + * + * @var int + */ + const COORDINATES = 102; + + /** + * IDD and area codes + * + * @var int + */ + const IDD_AREA = 103; + + /** + * Weather station name and code + * + * @var int + */ + const WEATHER_STATION = 104; + + /** + * MCC, MNC, and mobile carrier name + * + * @var int + */ + const MCC_MNC_MOBILE_CARRIER_NAME = 105; + + /** + * All fields at once + * + * @var int + */ + const ALL = 1001; + + /** + * Include the IP address of the looked up IP address + * + * @var int + */ + const IP_ADDRESS = 1002; + + /** + * Include the IP version of the looked up IP address + * + * @var int + */ + const IP_VERSION = 1003; + + /** + * Include the IP number of the looked up IP address + * + * @var int + */ + const IP_NUMBER = 1004; + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Exception code constants //////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Generic exception code + * + * @var int + */ + const EXCEPTION = 10000; + + /** + * No shmop extension found + * + * @var int + */ + const EXCEPTION_NO_SHMOP = 10001; + + /** + * Failed to open shmop memory segment for reading + * + * @var int + */ + const EXCEPTION_SHMOP_READING_FAILED = 10002; + + /** + * Failed to open shmop memory segment for writing + * + * @var int + */ + const EXCEPTION_SHMOP_WRITING_FAILED = 10003; + + /** + * Failed to create shmop memory segment + * + * @var int + */ + const EXCEPTION_SHMOP_CREATE_FAILED = 10004; + + /** + * The specified database file was not found + * + * @var int + */ + const EXCEPTION_DBFILE_NOT_FOUND = 10005; + + /** + * Not enough memory to load database file + * + * @var int + */ + const EXCEPTION_NO_MEMORY = 10006; + + /** + * No candidate databse files found + * + * @var int + */ + const EXCEPTION_NO_CANDIDATES = 10007; + + /** + * Failed to open database file + * + * @var int + */ + const EXCEPTION_FILE_OPEN_FAILED = 10008; + + /** + * Failed to determine the current path + * + * @var int + */ + const EXCEPTION_NO_PATH = 10009; + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Caching method constants //////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Directly read from the databse file + * + * @var int + */ + const FILE_IO = 100001; + + /** + * Read the whole database into a variable for caching + * + * @var int + */ + const MEMORY_CACHE = 100002; + + /** + * Use shared memory objects for caching + * + * @var int + */ + const SHARED_MEMORY = 100003; + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Shared memory constants ///////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Share memory segment's permissions (for creation) + * + * @var int + */ + const SHM_PERMS = 0600; + + /** + * Number of bytes to read/write at a time in order to load the shared memory cache (512k) + * + * @var int + */ + const SHM_CHUNK_SIZE = 524288; + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Static data ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Column offset mapping + * + * Each entry contains an array mapping databse version (0--23) to offset within a record. + * A value of 0 means the column is not present in the given database version. + * + * @access private + * @static + * @var array + */ + private static $columns = [ + self::COUNTRY_CODE => [8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8], + self::COUNTRY_NAME => [8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8], + self::REGION_NAME => [0, 0, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12], + self::CITY_NAME => [0, 0, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16], + self::LATITUDE => [0, 0, 0, 0, 20, 20, 0, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20], + self::LONGITUDE => [0, 0, 0, 0, 24, 24, 0, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24], + self::ISP => [0, 12, 0, 20, 0, 28, 20, 28, 0, 32, 0, 36, 0, 36, 0, 36, 0, 36, 28, 36, 0, 36, 28, 36], + self::DOMAIN_NAME => [0, 0, 0, 0, 0, 0, 24, 32, 0, 36, 0, 40, 0, 40, 0, 40, 0, 40, 32, 40, 0, 40, 32, 40], + self::ZIP_CODE => [0, 0, 0, 0, 0, 0, 0, 0, 28, 28, 28, 28, 0, 28, 28, 28, 0, 28, 0, 28, 28, 28, 0, 28], + self::TIME_ZONE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 32, 28, 32, 32, 32, 28, 32, 0, 32, 32, 32, 0, 32], + self::NET_SPEED => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 44, 0, 44, 32, 44, 0, 44, 0, 44, 0, 44], + self::IDD_CODE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 48, 0, 48, 0, 48, 36, 48, 0, 48], + self::AREA_CODE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 52, 0, 52, 0, 52, 40, 52, 0, 52], + self::WEATHER_STATION_CODE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 56, 0, 56, 0, 56, 0, 56], + self::WEATHER_STATION_NAME => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 60, 0, 60, 0, 60, 0, 60], + self::MCC => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 64, 0, 64, 36, 64], + self::MNC => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 40, 68, 0, 68, 40, 68], + self::MOBILE_CARRIER_NAME => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 72, 0, 72, 44, 72], + self::ELEVATION => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 44, 76, 0, 76], + self::USAGE_TYPE => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 48, 80], + ]; + + /** + * Column name mapping + * + * @access private + * @static + * @var array + */ + private static $names = [ + self::COUNTRY_CODE => 'countryCode', + self::COUNTRY_NAME => 'countryName', + self::REGION_NAME => 'regionName', + self::CITY_NAME => 'cityName', + self::LATITUDE => 'latitude', + self::LONGITUDE => 'longitude', + self::ISP => 'isp', + self::DOMAIN_NAME => 'domainName', + self::ZIP_CODE => 'zipCode', + self::TIME_ZONE => 'timeZone', + self::NET_SPEED => 'netSpeed', + self::IDD_CODE => 'iddCode', + self::AREA_CODE => 'areaCode', + self::WEATHER_STATION_CODE => 'weatherStationCode', + self::WEATHER_STATION_NAME => 'weatherStationName', + self::MCC => 'mcc', + self::MNC => 'mnc', + self::MOBILE_CARRIER_NAME => 'mobileCarrierName', + self::ELEVATION => 'elevation', + self::USAGE_TYPE => 'usageType', + self::IP_ADDRESS => 'ipAddress', + self::IP_VERSION => 'ipVersion', + self::IP_NUMBER => 'ipNumber', + ]; + + /** + * Database names, in order of preference for file lookup + * + * @var array + */ + private static $databases = [ + // IPv4 databases + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION-USAGETYPE', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ISP-DOMAIN-MOBILE-USAGETYPE', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-AREACODE-ELEVATION', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ISP-DOMAIN-MOBILE', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-NETSPEED-WEATHER', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-AREACODE', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-TIMEZONE-NETSPEED', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-ISP-DOMAIN', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ISP-DOMAIN', + 'IP-COUNTRY-REGION-CITY-ISP-DOMAIN', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ISP', + 'IP-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE', + 'IP-COUNTRY-REGION-CITY-ISP', + 'IP-COUNTRY-REGION-CITY', + 'IP-COUNTRY-ISP', + 'IP-COUNTRY', + // IPv6 databases + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION-USAGETYPE', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ISP-DOMAIN-MOBILE-USAGETYPE', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE-ELEVATION', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-AREACODE-ELEVATION', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER-MOBILE', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ISP-DOMAIN-MOBILE', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE-WEATHER', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-NETSPEED-WEATHER', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED-AREACODE', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-AREACODE', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN-NETSPEED', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-TIMEZONE-NETSPEED', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE-ISP-DOMAIN', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-TIMEZONE', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE-ISP-DOMAIN', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ZIPCODE', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ISP-DOMAIN', + 'IPV6-COUNTRY-REGION-CITY-ISP-DOMAIN', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE-ISP', + 'IPV6-COUNTRY-REGION-CITY-LATITUDE-LONGITUDE', + 'IPV6-COUNTRY-REGION-CITY-ISP', + 'IPV6-COUNTRY-REGION-CITY', + 'IPV6-COUNTRY-ISP', + 'IPV6-COUNTRY', + ]; + + /** + * Static memory buffer to use for MEMORY_CACHE mode, the keys will be BIN filenames and the values their contents + * + * @access private + * @static + * @var array + */ + private static $buffer = []; + + /** + * The machine's float size + * + * @access private + * @static + * @var int + */ + private static $floatSize = null; + + /** + * The configured memory limit + * + * @access private + * @static + * @var int + */ + private static $memoryLimit = null; + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Caching backend controls //////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Caching mode to use (one of FILE_IO, MEMORY_CACHE, or SHARED_MEMORY) + * + * @access private + * @var int + */ + private $mode; + + /** + * File pointer to use for FILE_IO mode, BIN filename for MEMORY_CACHE mode, or shared memory id to use for SHARED_MEMORY mode + * + * @access private + * @var resource|int|false + */ + private $resource = false; + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Database metadata /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Database's compilation date + * + * @access private + * @var int + */ + private $date; + + /** + * Database's type (0--23) + * + * @access private + * @var int + */ + private $type; + + /** + * Database's register width (as an array mapping 4 to IPv4 width, and 6 to IPv6 width) + * + * @access private + * @var array + */ + private $columnWidth = []; + + /** + * Database's pointer offset (as an array mapping 4 to IPv4 offset, and 6 to IPv6 offset) + * + * @access private + * @var array + */ + private $offset = []; + + /** + * Amount of IP address ranges the database contains (as an array mapping 4 to IPv4 count, and 6 to IPv6 count) + * + * @access private + * @var array + */ + private $ipCount = []; + + /** + * Offset withing the database where IP data begins (as an array mapping 4 to IPv4 base, and 6 to IPv6 base) + * + * @access private + * @var array + */ + private $ipBase = []; + + + //hjlim + private $indexBaseAddr = []; + private $year; + private $month; + private $day; + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Default fields ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Default fields to return during lookup + * + * @access private + * @var int|array + */ + private $defaultFields = self::ALL; + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Administrative public interface ///////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Constructor + * + * @access public + * @param string $file Filename of the BIN database to load + * @param int $mode Caching mode (one of FILE_IO, MEMORY_CACHE, or SHARED_MEMORY) + * @throws \Exception + */ + public function __construct($file = null, $mode = self::FILE_IO, $defaultFields = self::ALL) { + // find the referred file and its size + $rfile = self::findFile($file); + $size = filesize($rfile); + + // initialize caching backend + switch ($mode) { + case self::SHARED_MEMORY: + // verify the shmop extension is loaded + if (!extension_loaded('shmop')) { + throw new \Exception(__CLASS__ . ": Please make sure your PHP setup has the 'shmop' extension enabled.", self::EXCEPTION_NO_SHMOP); + } + + $limit = self::getMemoryLimit(); + if (false !== $limit && $size > $limit) { + throw new \Exception(__CLASS__ . ": Insufficient memory to load file '{$rfile}'.", self::EXCEPTION_NO_MEMORY); + } + + $this->mode = self::SHARED_MEMORY; + $shmKey = self::getShmKey($rfile); + + // try to open the shared memory segment + $this->resource = @shmop_open($shmKey, 'a', 0, 0); + if (false === $this->resource) { + // the segment did not exist, create it and load the database into it + $fp = fopen($rfile, 'rb'); + if (false === $fp) { + throw new \Exception(__CLASS__ . ": Unable to open file '{$rfile}'.", self::EXCEPTION_FILE_OPEN_FAILED); + } + + // try to open the memory segment for exclusive access + $shmId = @shmop_open($shmKey, 'n', self::SHM_PERMS, $size); + if (false === $shmId) { + throw new \Exception(__CLASS__ . ": Unable to create shared memory block '{$shmKey}'.", self::EXCEPTION_SHMOP_CREATE_FAILED); + } + + // load SHM_CHUNK_SIZE bytes at a time + $pointer = 0; + while ($pointer < $size) { + $buf = fread($fp, self::SHM_CHUNK_SIZE); + shmop_write($shmId, $buf, $pointer); + $pointer += self::SHM_CHUNK_SIZE; + } + shmop_close($shmId); + fclose($fp); + + // now open the memory segment for readonly access + $this->resource = @shmop_open($shmKey, 'a', 0, 0); + if (false === $this->resource) { + throw new \Exception(__CLASS__ . ": Unable to access shared memory block '{$shmKey}' for reading.", self::EXCEPTION_SHMOP_READING_FAILED); + } + } + break; + + case self::FILE_IO: + $this->mode = self::FILE_IO; + $this->resource = @fopen($rfile, 'rb'); + if (false === $this->resource) { + throw new \Exception(__CLASS__ . ": Unable to open file '{$rfile}'.", self::EXCEPTION_FILE_OPEN_FAILED); + } + break; + + case self::MEMORY_CACHE: + $this->mode = self::MEMORY_CACHE; + $this->resource = $rfile; + if (!array_key_exists($rfile, self::$buffer)) { + $limit = self::getMemoryLimit(); + if (false !== $limit && $size > $limit) { + throw new \Exception(__CLASS__ . ": Insufficient memory to load file '{$rfile}'.", self::EXCEPTION_NO_MEMORY); + } + + self::$buffer[$rfile] = @file_get_contents($rfile); + if (false === self::$buffer[$rfile]) { + throw new \Exception(__CLASS__ . ": Unable to open file '{$rfile}'.", self::EXCEPTION_FILE_OPEN_FAILED); + } + } + break; + + default: + } + + // determine the platform's float size + // + // NB: this should be a constant instead, and some unpack / typebanging magic + // should be used to accomodate different float sizes, but, as the libreary + // is written, this is the sanest thing to do anyway + // + if (null === self::$floatSize) { + self::$floatSize = strlen(pack('f', M_PI)); + } + + // set default fields to retrieve + $this->defaultFields = $defaultFields; + + // extract database metadata + $this->type = $this->readByte(1) - 1; + $this->columnWidth[4] = $this->readByte(2) * 4; + $this->columnWidth[6] = $this->columnWidth[4] + 12; + $this->offset[4] = -4; + $this->offset[6] = 8; + // + $this->year = 2000 + $this->readByte(3); + $this->month = $this->readByte(4); + $this->day = $this->readByte(5); + $this->date = date('Y-m-d', strtotime("{$this->year}-{$this->month}-{$this->day}")); + // + $this->ipCount[4] = $this->readWord(6); + $this->ipBase[4] = $this->readWord(10); //hjlim readword + $this->ipCount[6] = $this->readWord(14); + $this->ipBase[6] = $this->readWord(18); + $this->indexBaseAddr[4] = $this->readWord(22); //hjlim + $this->indexBaseAddr[6] = $this->readWord(26); //hjlim + } + + /** + * Destructor + * + * @access public + */ + public function __destruct() { + switch ($this->mode) { + case self::FILE_IO: + // free the file pointer + if (false !== $this->resource) { + fclose($this->resource); + $this->resource = false; + } + break; + case self::SHARED_MEMORY: + // detach from the memory segment + if (false !== $this->resource) { + shmop_close($this->resource); + $this->resource = false; + } + break; + } + } + + /** + * Tear down a shared memory segment created for the given file + * + * @access public + * @static + * @param string $file Filename of the BIN database whise segment must be deleted + * @throws \Exception + */ + public static function shmTeardown($file) { + // verify the shmop extension is loaded + if (!extension_loaded('shmop')) { + throw new \Exception(__CLASS__ . ": Please make sure your PHP setup has the 'shmop' extension enabled.", self::EXCEPTION_NO_SHMOP); + } + + // Get actual file path + $rfile = realpath($file); + + // If the file cannot be found, except away + if (false === $rfile) { + throw new \Exception(__CLASS__ . ": Database file '{$file}' does not seem to exist.", self::EXCEPTION_DBFILE_NOT_FOUND); + } + + $shmKey = self::getShmKey($rfile); + + // Try to open the memory segment for writing + $shmId = @shmop_open($shmKey, 'w', 0, 0); + if (false === $shmId) { + throw new \Exception(__CLASS__ . ": Unable to access shared memory block '{$shmKey}' for writing.", self::EXCEPTION_SHMOP_WRITING_FAILED); + } + + // Delete and close the descriptor + shmop_delete($shmId); + shmop_close($shmId); + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Static tools //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Get memory limit from the current PHP settings (return false if no memory limit set) + * + * @access private + * @static + * @return int|boolean + */ + private static function getMemoryLimit() { + // Get values if no cache + if (null === self::$memoryLimit) { + $limit = ini_get('memory_limit'); + + // Feal with defaults + if ('' === (string) $limit) { + $limit = '128M'; + } + + $value = (int) $limit; + + // Deal with "no-limit" + if ($value < 0) { + $value = false; + } else { + // Deal with shorthand bytes + switch (strtoupper(substr($limit, -1))) { + case 'G': $value *= 1024; + case 'M': $value *= 1024; + case 'K': $value *= 1024; + } + } + self::$memoryLimit = $value; + } + return self::$memoryLimit; + } + + /** + * Return the realpath of the given file or look for the first matching database option + * + * @param string $file File to try to find, or null to try the databases in turn on the current file's path + * @return string + * @throws \Exception + */ + private static function findFile($file = null) { + if (null !== $file) { + // Get actual file path + $rfile = realpath($file); + + // If the file cannot be found, except away + if (false === $rfile) { + throw new \Exception(__CLASS__ . ": Database file '{$file}' does not seem to exist.", self::EXCEPTION_DBFILE_NOT_FOUND); + } + + return $rfile; + } else { + // Try to get current path + $current = realpath(dirname(__FILE__)); + + if (false === $current) { + throw new \Exception(__CLASS__ . ": Cannot determine current path.", self::EXCEPTION_NO_PATH); + } + // Try each database in turn + foreach (self::$databases as $database) { + $rfile = realpath("{$current}/{$database}.BIN"); + if (false !== $rfile) { + return $rfile; + } + } + // No candidates found + throw new \Exception(__CLASS__ . ": No candidate database files found.", self::EXCEPTION_NO_CANDIDATES); + } + } + + /** + * Make the given number positive by wrapping it to 8 bit values + * + * @access private + * @static + * @param int $x Number to wrap + * @return int + */ + private static function wrap8($x) { + return $x + ($x < 0 ? 256 : 0); + } + + /** + * Make the given number positive by wrapping it to 32 bit values + * + * @access private + * @static + * @param int $x Number to wrap + * @return int + */ + private static function wrap32($x) { + return $x + ($x < 0 ? 4294967296 : 0); + } + + /** + * Generate a unique and repeatable shared memory key for each instance to use + * + * @access private + * @static + * @param string $filename Filename of the BIN file + * @return int + */ + private static function getShmKey($filename) { + // This will create a shared memory key that deterministically depends only on + // the current file's path and the BIN file's path + return (int) sprintf('%u', self::wrap32(crc32(__FILE__ . ':' . $filename))); + } + + /** + * Determine whether the given IP number of the given version lies between the given bounds + * + * This function will return 0 if the given ip number falls within the given bounds + * for the given version, -1 if it falls below, and 1 if it falls above. + * + * @access private + * @static + * @param int $version IP version to use (either 4 or 6) + * @param int|string $ip IP number to check (int for IPv4, string for IPv6) + * @param int|string $low Lower bound (int for IPv4, string for IPv6) + * @param int|string $high Uppoer bound (int for IPv4, string for IPv6) + * @return int + */ + private static function ipBetween($version, $ip, $low, $high) { + if (4 === $version) { + // Use normal PHP ints + if ($low <= $ip) { + if ($ip < $high) { + return 0; + } else { + return 1; + } + } else { + return -1; + } + } else { + // Use BCMath + if (bccomp($low, $ip, 0) <= 0) { + if (bccomp($ip, $high, 0) <= -1) { + return 0; + } else { + return 1; + } + } else { + return -1; + } + } + } + + /** + * Get the IP version and number of the given IP address + * + * This method will return an array, whose components will be: + * - first: 4 if the given IP address is an IPv4 one, 6 if it's an IPv6 one, + * or fase if it's neither. + * - second: the IP address' number if its version is 4, the number string if + * its version is 6, false otherwise. + * + * @access private + * @static + * @param string $ip IP address to extract the version and number for + * @return array + */ + private static function ipVersionAndNumber($ip) { + if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { + return [4, sprintf('%u', ip2long($ip))]; + } elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) { + $result = 0; + + foreach (str_split(bin2hex(inet_pton($ip)), 8) as $word) { + $result = bcadd(bcmul($result, '4294967296', 0), self::wrap32(hexdec($word)), 0); + } + + return [6, $result]; + } else { + // Invalid IP address, return falses + return [false, false]; + } + } + + /** + * Return the decimal string representing the binary data given + * + * @access private + * @static + * @param string $data Binary data to parse + * @return string + */ + private static function bcBin2Dec($data) { + $parts = array( + unpack('V', substr($data, 12, 4)), + unpack('V', substr($data, 8, 4)), + unpack('V', substr($data, 4, 4)), + unpack('V', substr($data, 0, 4)), + ); + + foreach($parts as &$part) + if($part[1] < 0) + $part[1] += 4294967296; + + $result = bcadd(bcadd(bcmul($parts[0][1], bcpow(4294967296, 3)), bcmul($parts[1][1], bcpow(4294967296, 2))), bcadd(bcmul($parts[2][1], 4294967296), $parts[3][1])); + + return $result; + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Caching backend abstraction ///////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Low level read function to abstract away the caching mode being used + * + * @access private + * @param int $pos Position from where to start reading + * @param int $len Read this many bytes + * @return string + */ + private function read($pos, $len) { + switch ($this->mode) { + case self::SHARED_MEMORY: + return shmop_read($this->resource, $pos, $len); + + case self::MEMORY_CACHE: + return $data = substr(self::$buffer[$this->resource], $pos, $len); + + default: + fseek($this->resource, $pos, SEEK_SET); + return fread($this->resource, $len); + } + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Low-level read functions //////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Low level function to fetch a string from the caching backend + * + * @access private + * @param int $pos Position to read from + * @param int $additional Additional offset to apply + * @return string + */ + private function readString($pos, $additional = 0) { + // Get the actual pointer to the string's head + $spos = $this->readWord($pos) + $additional; + + // Read as much as the length (first "string" byte) indicates + return $this->read($spos + 1, $this->readByte($spos + 1)); + } + + /** + * Low level function to fetch a float from the caching backend + * + * @access private + * @param int $pos Position to read from + * @return float + */ + private function readFloat($pos) { + // Unpack a float's size worth of data + return unpack('f', $this->read($pos - 1, self::$floatSize))[1]; + } + + /** + * Low level function to fetch a quadword (128 bits) from the caching backend + * + * @access private + * @param int $pos Position to read from + * @return string + */ + private function readQuad($pos) { + // Use BCMath ints to get a quad's (128-bit) value + return self::bcBin2Dec($this->read($pos - 1, 16)); + } + + /** + * Low level function to fetch a word (32 bits) from the caching backend + * + * @access private + * @param int $pos Position to read from + * @return int + */ + private function readWord($pos) { + // Unpack a long's worth of data + return self::wrap32(unpack('V', $this->read($pos - 1, 4))[1]); + } + + /** + * Low level function to fetch a byte from the caching backend + * + * @access private + * @param int $pos Position to read from + * @return string + */ + private function readByte($pos) { + // Unpack a byte's worth of data + return self::wrap8(unpack('C', $this->read($pos - 1, 1))[1]); + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // High-level read functions /////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * High level function to fetch the country name and code + * + * @access private + * @param int|boolean $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * @return array + */ + private function readCountryNameAndCode($pointer) { + if (false === $pointer) { + // Deal with invalid IPs + $countryCode = self::INVALID_IP_ADDRESS; + $countryName = self::INVALID_IP_ADDRESS; + } elseif (0 === self::$columns[self::COUNTRY_CODE][$this->type]) { + // If the field is not suported, return accordingly + $countryCode = self::FIELD_NOT_SUPPORTED; + $countryName = self::FIELD_NOT_SUPPORTED; + } else { + // Read the country code and name (the name shares the country's pointer, + // but it must be artificially displaced 3 bytes ahead: 2 for the country code, one + // for the country name's length) + $countryCode = $this->readString($pointer + self::$columns[self::COUNTRY_CODE][$this->type]); + $countryName = $this->readString($pointer + self::$columns[self::COUNTRY_NAME][$this->type], 3); + } + + return [$countryName, $countryCode]; + } + + /** + * High level function to fetch the region name + * + * @access private + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * @return string + */ + private function readRegionName($pointer) { + if (false === $pointer) { + // Deal with invalid IPs + $regionName = self::INVALID_IP_ADDRESS; + } elseif (0 === self::$columns[self::REGION_NAME][$this->type]) { + // If the field is not suported, return accordingly + $regionName = self::FIELD_NOT_SUPPORTED; + } else { + // Read the region name + $regionName = $this->readString($pointer + self::$columns[self::REGION_NAME][$this->type]); + } + return $regionName; + } + + /** + * High level function to fetch the city name + * + * @access private + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * @return string + */ + private function readCityName($pointer) { + if (false === $pointer) { + // Deal with invalid IPs + $cityName = self::INVALID_IP_ADDRESS; + } elseif (0 === self::$columns[self::CITY_NAME][$this->type]) { + // If the field is not suported, return accordingly + $cityName = self::FIELD_NOT_SUPPORTED; + } else { + // Read the city name + $cityName = $this->readString($pointer + self::$columns[self::CITY_NAME][$this->type]); + } + return $cityName; + } + + /** + * High level function to fetch the latitude and longitude + * + * @access private + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * @return array + */ + private function readLatitudeAndLongitude($pointer) { + if (false === $pointer) { + // Deal with invalid IPs + $latitude = self::INVALID_IP_ADDRESS; + $longitude = self::INVALID_IP_ADDRESS; + } elseif (0 === self::$columns[self::LATITUDE][$this->type]) { + // If the field is not suported, return accordingly + $latitude = self::FIELD_NOT_SUPPORTED; + $longitude = self::FIELD_NOT_SUPPORTED; + } else { + // Read latitude and longitude + $latitude = $this->readFloat($pointer + self::$columns[self::LATITUDE][$this->type]); + $longitude = $this->readFloat($pointer + self::$columns[self::LONGITUDE][$this->type]); + } + return [$latitude, $longitude]; + } + + /** + * High level function to fetch the ISP name + * + * @access private + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * @return string + */ + private function readIsp($pointer) { + if (false === $pointer) { + // Deal with invalid IPs + $isp = self::INVALID_IP_ADDRESS; + } elseif (0 === self::$columns[self::ISP][$this->type]) { + // If the field is not suported, return accordingly + $isp = self::FIELD_NOT_SUPPORTED; + } else { + // Read isp name + $isp = $this->readString($pointer + self::$columns[self::ISP][$this->type]); + } + return $isp; + } + + /** + * High level function to fetch the domain name + * + * @access private + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * @return string + */ + private function readDomainName($pointer) { + if (false === $pointer) { + // Deal with invalid IPs + $domainName = self::INVALID_IP_ADDRESS; + } elseif (0 === self::$columns[self::DOMAIN_NAME][$this->type]) { + // If the field is not suported, return accordingly + $domainName = self::FIELD_NOT_SUPPORTED; + } else { + // Read the domain name + $domainName = $this->readString($pointer + self::$columns[self::DOMAIN_NAME][$this->type]); + } + return $domainName; + } + + /** + * High level function to fetch the zip code + * + * @access private + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * @return string + */ + private function readZipCode($pointer) { + if (false === $pointer) { + // Deal with invalid IPs + $zipCode = self::INVALID_IP_ADDRESS; + } elseif (0 === self::$columns[self::ZIP_CODE][$this->type]) { + // If the field is not suported, return accordingly + $zipCode = self::FIELD_NOT_SUPPORTED; + } else { + // Read the zip code + $zipCode = $this->readString($pointer + self::$columns[self::ZIP_CODE][$this->type]); + } + return $zipCode; + } + + /** + * High level function to fetch the time zone + * + * @access private + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * @return string + */ + private function readTimeZone($pointer) { + if (false === $pointer) { + // Deal with invalid IPs + $timeZone = self::INVALID_IP_ADDRESS; + } elseif (0 === self::$columns[self::TIME_ZONE][$this->type]) { + // If the field is not suported, return accordingly + $timeZone = self::FIELD_NOT_SUPPORTED; + } else { + // Read the time zone + $timeZone = $this->readString($pointer + self::$columns[self::TIME_ZONE][$this->type]); + } + return $timeZone; + } + + /** + * High level function to fetch the net speed + * + * @access private + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * @return string + */ + private function readNetSpeed($pointer) { + if (false === $pointer) { + // Deal with invalid IPs + $netSpeed = self::INVALID_IP_ADDRESS; + } elseif (0 === self::$columns[self::NET_SPEED][$this->type]) { + // If the field is not suported, return accordingly + $netSpeed = self::FIELD_NOT_SUPPORTED; + } else { + // Read the net speed + $netSpeed = $this->readString($pointer + self::$columns[self::NET_SPEED][$this->type]); + } + return $netSpeed; + } + + /** + * High level function to fetch the IDD and area codes + * + * @access private + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * @return array + */ + private function readIddAndAreaCodes($pointer) { + if (false === $pointer) { + // Deal with invalid IPs + $iddCode = self::INVALID_IP_ADDRESS; + $areaCode = self::INVALID_IP_ADDRESS; + } elseif (0 === self::$columns[self::IDD_CODE][$this->type]) { + // If the field is not suported, return accordingly + $iddCode = self::FIELD_NOT_SUPPORTED; + $areaCode = self::FIELD_NOT_SUPPORTED; + } else { + // Read IDD and area codes + $iddCode = $this->readString($pointer + self::$columns[self::IDD_CODE][$this->type]); + $areaCode = $this->readString($pointer + self::$columns[self::AREA_CODE][$this->type]); + } + return [$iddCode, $areaCode]; + } + + /** + * High level function to fetch the weather station name and code + * + * @access private + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * @return array + */ + private function readWeatherStationNameAndCode($pointer) { + if (false === $pointer) { + // Deal with invalid IPs + $weatherStationName = self::INVALID_IP_ADDRESS; + $weatherStationCode = self::INVALID_IP_ADDRESS; + } elseif (0 === self::$columns[self::WEATHER_STATION_NAME][$this->type]) { + // If the field is not suported, return accordingly + $weatherStationName = self::FIELD_NOT_SUPPORTED; + $weatherStationCode = self::FIELD_NOT_SUPPORTED; + } else { + // Read weather station name and code + $weatherStationName = $this->readString($pointer + self::$columns[self::WEATHER_STATION_NAME][$this->type]); + $weatherStationCode = $this->readString($pointer + self::$columns[self::WEATHER_STATION_CODE][$this->type]); + } + return [$weatherStationName, $weatherStationCode]; + } + + /** + * High level function to fetch the MCC, MNC, and mobile carrier name + * + * @access private + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * @return array + */ + private function readMccMncAndMobileCarrierName($pointer) { + if (false === $pointer) { + // Deal with invalid IPs + $mcc = self::INVALID_IP_ADDRESS; + $mnc = self::INVALID_IP_ADDRESS; + $mobileCarrierName = self::INVALID_IP_ADDRESS; + } elseif (0 === self::$columns[self::MCC][$this->type]) { + // If the field is not suported, return accordingly + $mcc = self::FIELD_NOT_SUPPORTED; + $mnc = self::FIELD_NOT_SUPPORTED; + $mobileCarrierName = self::FIELD_NOT_SUPPORTED; + } else { + // Read MCC, MNC, and mobile carrier name + $mcc = $this->readString($pointer + self::$columns[self::MCC][$this->type]); + $mnc = $this->readString($pointer + self::$columns[self::MNC][$this->type]); + $mobileCarrierName = $this->readString($pointer + self::$columns[self::MOBILE_CARRIER_NAME][$this->type]); + } + return [$mcc, $mnc, $mobileCarrierName]; + } + + /** + * High level function to fetch the elevation + * + * @access private + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * @return string + */ + private function readElevation($pointer) { + if (false === $pointer) { + // Deal with invalid IPs + $elevation = self::INVALID_IP_ADDRESS; + } elseif (0 === self::$columns[self::ELEVATION][$this->type]) { + // If the field is not suported, return accordingly + $elevation = self::FIELD_NOT_SUPPORTED; + } else { + // Read the elevation + $elevation = $this->readString($pointer + self::$columns[self::ELEVATION][$this->type]); + } + return $elevation; + } + + /** + * High level function to fetch the usage type + * + * @access private + * @param int $pointer Position to read from, if false, return self::INVALID_IP_ADDRESS + * @return string + */ + private function readUsageType($pointer) { + if (false === $pointer) { + // Deal with invalid IPs + $usageType = self::INVALID_IP_ADDRESS; + } elseif (0 === self::$columns[self::USAGE_TYPE][$this->type]) { + // If the field is not suported, return accordingly + $usageType = self::FIELD_NOT_SUPPORTED; + } else { + $usageType = $this->readString($pointer + self::$columns[self::USAGE_TYPE][$this->type]); + } + return $usageType; + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Binary search and support functions ///////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * High level fucntion to read an IP address of the given version + * + * @access private + * @param int $version IP version to read (either 4 or 6, returns false on anything else) + * @param int $pos Position to read from + * @return int|string|boolean + */ + private function readIp($version, $pos) { + if (4 === $version) { + // Read a standard PHP int + return self::wrap32($this->readWord($pos)); + } elseif (6 === $version) { + // Read as BCMath int (quad) + return $this->readQuad($pos); + } else { + // unrecognized + return false; + } + } + + /** + * Perform a binary search on the given IP number and return a pointer to its record + * + * @access private + * @param int $version IP version to use for searching + * @param int $ipNumber IP number to look for + * @return int|boolean + */ + private function binSearch($version, $ipNumber) { + if (false === $version) { + // unrecognized version + return false; + } + + // initialize fields + $base = $this->ipBase[$version]; + $offset = $this->offset[$version]; + $width = $this->columnWidth[$version]; + $high = $this->ipCount[$version]; + $low = 0; + + //hjlim + $indexBaseStart = $this->indexBaseAddr[$version]; + if ($indexBaseStart > 0){ + $indexPos = 0; + switch($version){ + case 4: + $ipNum1_2 = intval($ipNumber / 65536); + $indexPos = $indexBaseStart + ($ipNum1_2 << 3); + + break; + + case 6: + $ipNum1 = intval(bcdiv($ipNumber, bcpow('2', '112'))); + $indexPos = $indexBaseStart + ($ipNum1 << 3); + + break; + + default: + return false; + } + + $low = $this->readWord($indexPos); + $high = $this->readWord($indexPos + 4); + } + + // as long as we can narrow down the search... + while ($low <= $high) { + $mid = (int) ($low + (($high - $low) >> 1)); + + // Read IP ranges to get boundaries + $ip_from = $this->readIp($version, $base + $width * $mid); + $ip_to = $this->readIp($version, $base + $width * ($mid + 1)); + + // determine whether to return, repeat on the lower half, or repeat on the upper half + switch (self::ipBetween($version, $ipNumber, $ip_from, $ip_to)) { + case 0: + return $base + $offset + $mid * $width; + case -1: + $high = $mid - 1; + break; + case 1: + $low = $mid + 1; + break; + } + } + + // nothing found + return false; + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // Public interface //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Get the database's compilation date as a string of the form 'YYYY-MM-DD' + * + * @access public + * @return string + */ + public function getDate() { + return $this->date; + } + + /** + * Get the database's type (1--24) + * + * @access public + * @return int + */ + public function getType() { + return $this->type + 1; + } + + /** + * Return this database's available fields + * + * @access public + * @param boolean $asNames Whether to return the mapped names intead of numbered constants + * @return array + */ + public function getFields($asNames = false) { + $result = array_keys(array_filter(self::$columns, function ($field) { + return 0 !== $field[$this->type]; + })); + if ($asNames) { + $return = []; + foreach ($result as $field) { + $return[] = self::$names[$field]; + } + return $return; + } else { + return $result; + } + } + + /** + * Return the version of module + */ + public function getModuleVersion() { + return self::VERSION; + } + + /** + * Return the version of module + */ + public function getDatabaseVersion() { + return $this->year . '.' . $this->month . '.' . $this->day; + } + + /** + * This function will look the given IP address up in the database and return the result(s) asked for + * + * If a single, SINGULAR, field is specified, only its mapped value is returned. + * If many fields are given (as an array) or a MULTIPLE field is specified, an + * array whith the returned singular field names as keys and their corresponding + * values is returned. + * + * @access public + * @param string $ip IP address to look up + * @param int|array $fields Field(s) to return + * @param boolean $asNamed Whether to return an associative array instead + * @return mixed|array|boolean + */ + public function lookup($ip, $fields = null, $asNamed = true) { + // extract IP version and number + list($ipVersion, $ipNumber) = self::ipVersionAndNumber($ip); + // perform the binary search proper (if the IP address was invalid, binSearch will return false) + $pointer = $this->binSearch($ipVersion, $ipNumber); + + // apply defaults if needed + if (null === $fields) { + $fields = $this->defaultFields; + } + + // turn fields into an array in case it wasn't already + $ifields = (array) $fields; + // add fields if needed + if (in_array(self::ALL, $ifields)) { + $ifields[] = self::REGION_NAME; + $ifields[] = self::CITY_NAME; + $ifields[] = self::ISP; + $ifields[] = self::DOMAIN_NAME; + $ifields[] = self::ZIP_CODE; + $ifields[] = self::TIME_ZONE; + $ifields[] = self::NET_SPEED; + $ifields[] = self::ELEVATION; + $ifields[] = self::USAGE_TYPE; + // + $ifields[] = self::COUNTRY; + $ifields[] = self::COORDINATES; + $ifields[] = self::IDD_AREA; + $ifields[] = self::WEATHER_STATION; + $ifields[] = self::MCC_MNC_MOBILE_CARRIER_NAME; + // + $ifields[] = self::IP_ADDRESS; + $ifields[] = self::IP_VERSION; + $ifields[] = self::IP_NUMBER; + } + // turn into a uniquely-valued array the fast way + // (see: http://php.net/manual/en/function.array-unique.php#77743) + $afields = array_keys(array_flip($ifields)); + // sorting them in reverse order warrants that by the time we get to + // SINGULAR fields, its MULTIPLE counterparts, if at all present, have + // already been retrieved + rsort($afields); + + // maintain a list of already retrieved fields to avoid doing it twice + $done = [ + self::COUNTRY_CODE => false, + self::COUNTRY_NAME => false, + self::REGION_NAME => false, + self::CITY_NAME => false, + self::LATITUDE => false, + self::LONGITUDE => false, + self::ISP => false, + self::DOMAIN_NAME => false, + self::ZIP_CODE => false, + self::TIME_ZONE => false, + self::NET_SPEED => false, + self::IDD_CODE => false, + self::AREA_CODE => false, + self::WEATHER_STATION_CODE => false, + self::WEATHER_STATION_NAME => false, + self::MCC => false, + self::MNC => false, + self::MOBILE_CARRIER_NAME => false, + self::ELEVATION => false, + self::USAGE_TYPE => false, + // + self::COUNTRY => false, + self::COORDINATES => false, + self::IDD_AREA => false, + self::WEATHER_STATION => false, + self::MCC_MNC_MOBILE_CARRIER_NAME => false, + // + self::IP_ADDRESS => false, + self::IP_VERSION => false, + self::IP_NUMBER => false, + ]; + // results are empty to begin with + $results = []; + + // treat each field in turn + foreach ($afields as $afield) { + switch ($afield) { + // purposefully ignore self::ALL, we already dealt with it + case self::ALL: break; + // + case self::COUNTRY: + if (!$done[self::COUNTRY]) { + list($results[self::COUNTRY_NAME], $results[self::COUNTRY_CODE]) = $this->readCountryNameAndCode($pointer); + $done[self::COUNTRY] = true; + $done[self::COUNTRY_CODE] = true; + $done[self::COUNTRY_NAME] = true; + } + break; + case self::COORDINATES: + if (!$done[self::COORDINATES]) { + list($results[self::LATITUDE], $results[self::LONGITUDE]) = $this->readLatitudeAndLongitude($pointer); + $done[self::COORDINATES] = true; + $done[self::LATITUDE] = true; + $done[self::LONGITUDE] = true; + } + break; + case self::IDD_AREA: + if (!$done[self::IDD_AREA]) { + list($results[self::IDD_CODE], $results[self::AREA_CODE]) = $this->readIddAndAreaCodes($pointer); + $done[self::IDD_AREA] = true; + $done[self::IDD_CODE] = true; + $done[self::AREA_CODE] = true; + } + break; + case self::WEATHER_STATION: + if (!$done[self::WEATHER_STATION]) { + list($results[self::WEATHER_STATION_NAME], $results[self::WEATHER_STATION_CODE]) = $this->readWeatherStationNameAndCode($pointer); + $done[self::WEATHER_STATION] = true; + $done[self::WEATHER_STATION_NAME] = true; + $done[self::WEATHER_STATION_CODE] = true; + } + break; + case self::MCC_MNC_MOBILE_CARRIER_NAME: + if (!$done[self::MCC_MNC_MOBILE_CARRIER_NAME]) { + list($results[self::MCC], $results[self::MNC], $results[self::MOBILE_CARRIER_NAME]) = $this->readMccMncAndMobileCarrierName($pointer); + $done[self::MCC_MNC_MOBILE_CARRIER_NAME] = true; + $done[self::MCC] = true; + $done[self::MNC] = true; + $done[self::MOBILE_CARRIER_NAME] = true; + } + break; + // + case self::COUNTRY_CODE: + if (!$done[self::COUNTRY_CODE]) { + $results[self::COUNTRY_CODE] = $this->readCountryNameAndCode($pointer)[1]; + $done[self::COUNTRY_CODE] = true; + } + break; + case self::COUNTRY_NAME: + if (!$done[self::COUNTRY_NAME]) { + $results[self::COUNTRY_NAME] = $this->readCountryNameAndCode($pointer)[0]; + $done[self::COUNTRY_NAME] = true; + } + break; + case self::REGION_NAME: + if (!$done[self::REGION_NAME]) { + $results[self::REGION_NAME] = $this->readRegionName($pointer); + $done[self::REGION_NAME] = true; + } + break; + case self::CITY_NAME: + if (!$done[self::CITY_NAME]) { + $results[self::CITY_NAME] = $this->readCityName($pointer); + $done[self::CITY_NAME] = true; + } + break; + case self::LATITUDE: + if (!$done[self::LATITUDE]) { + $results[self::LATITUDE] = $this->readLatitudeAndLongitude($pointer)[0]; + $done[self::LATITUDE] = true; + } + break; + case self::LONGITUDE: + if (!$done[self::LONGITUDE]) { + $results[self::LONGITUDE] = $this->readLatitudeAndLongitude($pointer)[1]; + $done[self::LONGITUDE] = true; + } + break; + case self::ISP: + if (!$done[self::ISP]) { + $results[self::ISP] = $this->readIsp($pointer); + $done[self::ISP] = true; + } + break; + case self::DOMAIN_NAME: + if (!$done[self::DOMAIN_NAME]) { + $results[self::DOMAIN_NAME] = $this->readDomainName($pointer); + $done[self::DOMAIN_NAME] = true; + } + break; + case self::ZIP_CODE: + if (!$done[self::ZIP_CODE]) { + $results[self::ZIP_CODE] = $this->readZipCode($pointer); + $done[self::ZIP_CODE] = true; + } + break; + case self::TIME_ZONE: + if (!$done[self::TIME_ZONE]) { + $results[self::TIME_ZONE] = $this->readTimeZone($pointer); + $done[self::TIME_ZONE] = true; + } + break; + case self::NET_SPEED: + if (!$done[self::NET_SPEED]) { + $results[self::NET_SPEED] = $this->readNetSpeed($pointer); + $done[self::NET_SPEED] = true; + } + break; + case self::IDD_CODE: + if (!$done[self::IDD_CODE]) { + $results[self::IDD_CODE] = $this->readIddAndAreaCodes($pointer)[0]; + $done[self::IDD_CODE] = true; + } + break; + case self::AREA_CODE: + if (!$done[self::AREA_CODE]) { + $results[self::AREA_CODE] = $this->readIddAndAreaCodes($pointer)[1]; + $done[self::AREA_CODE] = true; + } + break; + case self::WEATHER_STATION_CODE: + if (!$done[self::WEATHER_STATION_CODE]) { + $results[self::WEATHER_STATION_CODE] = $this->readWeatherStationNameAndCode($pointer)[1]; + $done[self::WEATHER_STATION_CODE] = true; + } + break; + case self::WEATHER_STATION_NAME: + if (!$done[self::WEATHER_STATION_NAME]) { + $results[self::WEATHER_STATION_NAME] = $this->readWeatherStationNameAndCode($pointer)[0]; + $done[self::WEATHER_STATION_NAME] = true; + } + break; + case self::MCC: + if (!$done[self::MCC]) { + $results[self::MCC] = $this->readMccMncAndMobileCarrierName($pointer)[0]; + $done[self::MCC] = true; + } + break; + case self::MNC: + if (!$done[self::MNC]) { + $results[self::MNC] = $this->readMccMncAndMobileCarrierName($pointer)[1]; + $done[self::MNC] = true; + } + break; + case self::MOBILE_CARRIER_NAME: + if (!$done[self::MOBILE_CARRIER_NAME]) { + $results[self::MOBILE_CARRIER_NAME] = $this->readMccMncAndMobileCarrierName($pointer)[2]; + $done[self::MOBILE_CARRIER_NAME] = true; + } + break; + case self::ELEVATION: + if (!$done[self::ELEVATION]) { + $results[self::ELEVATION] = $this->readElevation($pointer); + $done[self::ELEVATION] = true; + } + break; + case self::USAGE_TYPE: + if (!$done[self::USAGE_TYPE]) { + $results[self::USAGE_TYPE] = $this->readUsageType($pointer); + $done[self::USAGE_TYPE] = true; + } + break; + // + case self::IP_ADDRESS: + if (!$done[self::IP_ADDRESS]) { + $results[self::IP_ADDRESS] = $ip; + $done[self::IP_ADDRESS] = true; + } + break; + case self::IP_VERSION: + if (!$done[self::IP_VERSION]) { + $results[self::IP_VERSION] = $ipVersion; + $done[self::IP_VERSION] = true; + } + break; + case self::IP_NUMBER: + if (!$done[self::IP_NUMBER]) { + $results[self::IP_NUMBER] = $ipNumber; + $done[self::IP_NUMBER] = true; + } + break; + // + default: + $results[$afield] = self::FIELD_NOT_KNOWN; + } + } + + // If we were asked for an array, or we have multiple results to return... + if (is_array($fields) || count($results) > 1) { + // return array + if ($asNamed) { + // apply translations if needed + $return = []; + foreach ($results as $key => $val) { + if (array_key_exists($key, static::$names)) { + $return[static::$names[$key]] = $val; + } else { + $return[$key] = $val; + } + } + return $return; + } else { + return $results; + } + } else { + // return a single value + return array_values($results)[0]; + } + } + +} \ No newline at end of file diff --git a/application/libraries/lib_login.php b/application/libraries/lib_login.php new file mode 100644 index 0000000..a4b6e59 --- /dev/null +++ b/application/libraries/lib_login.php @@ -0,0 +1,115 @@ +ci =& get_instance(); + $this->ci->load->library('session'); + $this->ci->config->load('facebook'); + + if (! isset($_SESSION)) { + session_start(); + } + } + + public function facebook() + { + $facebook_default_scope = explode(',', $this->ci->config->item("facebook_default_scope")); + $facebook_app_id = $this->ci->config->item("facebook_app_id"); + $facebook_api_secret = $this->ci->config->item("facebook_api_secret"); + + // init app with app id and secret + FacebookSession::setDefaultApplication($facebook_app_id, $facebook_api_secret); + + // login helper with redirect_uri + $helper = new FacebookRedirectLoginHelper(site_url('login/facebook')); + // see if a existing session exists + if (isset($_SESSION) && isset($_SESSION['fb_token'])) { + // create new session from saved access_token + $session = new FacebookSession($_SESSION['fb_token']); + + // validate the access_token to make sure it's still valid + try { + if (!$session->validate()) { + $session = null; + } + } catch (Exception $e) { + // catch any exceptions + $session = null; + } + } + + if (!isset($session) || $session === null) { + // no session exists + + try { + $session = $helper->getSessionFromRedirect(); + } catch(FacebookRequestException $ex) { + // When Facebook returns an error + // handle this better in production code + print_r($ex); + } catch(Exception $ex) { + // When validation fails or other local issues + // handle this better in production code + print_r($ex); + } + } + + // see if we have a session + if (isset($session)) { + // save the session + $_SESSION['fb_token'] = $session->getToken(); + // create a session using saved token or the new one we generated at login + $session = new FacebookSession($session->getToken()); + + // graph api request for user data + $request = new FacebookRequest($session, 'GET', '/me'); + $response = $request->execute(); + // get response + $graphObject = $response->getGraphObject()->asArray(); + $fb_data = array( + 'me' => $graphObject, + 'loginUrl' => $helper->getLoginUrl($facebook_default_scope) + ); + $this->ci->session->set_userdata('fb_data', $fb_data); + + } else { + $fb_data = array( + 'me' => null, + 'loginUrl' => $helper->getLoginUrl($facebook_default_scope) + ); + $this->ci->session->set_userdata('fb_data', $fb_data); + } + + return $fb_data; + } +} diff --git a/application/libraries/phpass-0.1/PasswordHash.php b/application/libraries/phpass-0.1/PasswordHash.php new file mode 100644 index 0000000..550a01b --- /dev/null +++ b/application/libraries/phpass-0.1/PasswordHash.php @@ -0,0 +1,248 @@ + in 2004-2006 and placed in +# the public domain. +# +# There's absolutely no warranty. +# +# The homepage URL for this framework is: +# +# http://www.openwall.com/phpass/ +# +# Please be sure to update the Version line if you edit this file in any way. +# It is suggested that you leave the main version number intact, but indicate +# your project name (after the slash) and add your own revision information. +# +# Please do not change the "private" password hashing method implemented in +# here, thereby making your hashes incompatible. However, if you must, please +# change the hash type identifier (the "$P$") to something different. +# +# Obviously, since this code is in the public domain, the above are not +# requirements (there can be none), but merely suggestions. +# +class PasswordHash { + var $itoa64; + var $iteration_count_log2; + var $portable_hashes; + var $random_state; + + function PasswordHash($iteration_count_log2, $portable_hashes) + { + $this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; + + if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31) + $iteration_count_log2 = 8; + $this->iteration_count_log2 = $iteration_count_log2; + + $this->portable_hashes = $portable_hashes; + + $this->random_state = microtime() . getmypid(); + } + + function get_random_bytes($count) + { + $output = ''; + if (($fh = @fopen('/dev/urandom', 'rb'))) { + $output = fread($fh, $count); + fclose($fh); + } + + if (strlen($output) < $count) { + $output = ''; + for ($i = 0; $i < $count; $i += 16) { + $this->random_state = + md5(microtime() . $this->random_state); + $output .= + pack('H*', md5($this->random_state)); + } + $output = substr($output, 0, $count); + } + + return $output; + } + + function encode64($input, $count) + { + $output = ''; + $i = 0; + do { + $value = ord($input[$i++]); + $output .= $this->itoa64[$value & 0x3f]; + if ($i < $count) + $value |= ord($input[$i]) << 8; + $output .= $this->itoa64[($value >> 6) & 0x3f]; + if ($i++ >= $count) + break; + if ($i < $count) + $value |= ord($input[$i]) << 16; + $output .= $this->itoa64[($value >> 12) & 0x3f]; + if ($i++ >= $count) + break; + $output .= $this->itoa64[($value >> 18) & 0x3f]; + } while ($i < $count); + + return $output; + } + + function gensalt_private($input) + { + $output = '$P$'; + $output .= $this->itoa64[min($this->iteration_count_log2 + + ((PHP_VERSION >= '5') ? 5 : 3), 30)]; + $output .= $this->encode64($input, 6); + + return $output; + } + + function crypt_private($password, $setting) + { + $output = '*0'; + if (substr($setting, 0, 2) == $output) + $output = '*1'; + + if (substr($setting, 0, 3) != '$P$') + return $output; + + $count_log2 = strpos($this->itoa64, $setting[3]); + if ($count_log2 < 7 || $count_log2 > 30) + return $output; + + $count = 1 << $count_log2; + + $salt = substr($setting, 4, 8); + if (strlen($salt) != 8) + return $output; + + # We're kind of forced to use MD5 here since it's the only + # cryptographic primitive available in all versions of PHP + # currently in use. To implement our own low-level crypto + # in PHP would result in much worse performance and + # consequently in lower iteration counts and hashes that are + # quicker to crack (by non-PHP code). + if (PHP_VERSION >= '5') { + $hash = md5($salt . $password, TRUE); + do { + $hash = md5($hash . $password, TRUE); + } while (--$count); + } else { + $hash = pack('H*', md5($salt . $password)); + do { + $hash = pack('H*', md5($hash . $password)); + } while (--$count); + } + + $output = substr($setting, 0, 12); + $output .= $this->encode64($hash, 16); + + return $output; + } + + function gensalt_extended($input) + { + $count_log2 = min($this->iteration_count_log2 + 8, 24); + # This should be odd to not reveal weak DES keys, and the + # maximum valid value is (2**24 - 1) which is odd anyway. + $count = (1 << $count_log2) - 1; + + $output = '_'; + $output .= $this->itoa64[$count & 0x3f]; + $output .= $this->itoa64[($count >> 6) & 0x3f]; + $output .= $this->itoa64[($count >> 12) & 0x3f]; + $output .= $this->itoa64[($count >> 18) & 0x3f]; + + $output .= $this->encode64($input, 3); + + return $output; + } + + function gensalt_blowfish($input) + { + # This one needs to use a different order of characters and a + # different encoding scheme from the one in encode64() above. + # We care because the last character in our encoded string will + # only represent 2 bits. While two known implementations of + # bcrypt will happily accept and correct a salt string which + # has the 4 unused bits set to non-zero, we do not want to take + # chances and we also do not want to waste an additional byte + # of entropy. + $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + + $output = '$2a$'; + $output .= chr(ord('0') + $this->iteration_count_log2 / 10); + $output .= chr(ord('0') + $this->iteration_count_log2 % 10); + $output .= '$'; + + $i = 0; + do { + $c1 = ord($input[$i++]); + $output .= $itoa64[$c1 >> 2]; + $c1 = ($c1 & 0x03) << 4; + if ($i >= 16) { + $output .= $itoa64[$c1]; + break; + } + + $c2 = ord($input[$i++]); + $c1 |= $c2 >> 4; + $output .= $itoa64[$c1]; + $c1 = ($c2 & 0x0f) << 2; + + $c2 = ord($input[$i++]); + $c1 |= $c2 >> 6; + $output .= $itoa64[$c1]; + $output .= $itoa64[$c2 & 0x3f]; + } while (1); + + return $output; + } + + function HashPassword($password) + { + $random = ''; + + if (CRYPT_BLOWFISH == 1 && !$this->portable_hashes) { + $random = $this->get_random_bytes(16); + $hash = + crypt($password, $this->gensalt_blowfish($random)); + if (strlen($hash) == 60) + return $hash; + } + + if (CRYPT_EXT_DES == 1 && !$this->portable_hashes) { + if (strlen($random) < 3) + $random = $this->get_random_bytes(3); + $hash = + crypt($password, $this->gensalt_extended($random)); + if (strlen($hash) == 20) + return $hash; + } + + if (strlen($random) < 6) + $random = $this->get_random_bytes(6); + $hash = + $this->crypt_private($password, + $this->gensalt_private($random)); + if (strlen($hash) == 34) + return $hash; + + # Returning '*' on error is safe here, but would _not_ be safe + # in a crypt(3)-like function used _both_ for generating new + # hashes and for validating passwords against existing hashes. + return '*'; + } + + function CheckPassword($password, $stored_hash) + { + $hash = $this->crypt_private($password, $stored_hash); + if ($hash[0] == '*') + $hash = crypt($password, $stored_hash); + + return $hash == $stored_hash; + } +} + +?> diff --git a/application/libraries/phpass-0.1/c/Makefile b/application/libraries/phpass-0.1/c/Makefile new file mode 100644 index 0000000..fe48917 --- /dev/null +++ b/application/libraries/phpass-0.1/c/Makefile @@ -0,0 +1,21 @@ +# +# Written by Solar Designer and placed in the public domain. +# See crypt_private.c for more information. +# +CC = gcc +LD = $(CC) +RM = rm -f +CFLAGS = -Wall -O2 -fomit-frame-pointer -funroll-loops +LDFLAGS = -s +LIBS = -lcrypto + +all: crypt_private-test + +crypt_private-test: crypt_private-test.o + $(LD) $(LDFLAGS) $(LIBS) crypt_private-test.o -o $@ + +crypt_private-test.o: crypt_private.c + $(CC) -c $(CFLAGS) crypt_private.c -DTEST -o $@ + +clean: + $(RM) crypt_private-test* diff --git a/application/libraries/phpass-0.1/c/crypt_private.c b/application/libraries/phpass-0.1/c/crypt_private.c new file mode 100644 index 0000000..669c4ae --- /dev/null +++ b/application/libraries/phpass-0.1/c/crypt_private.c @@ -0,0 +1,106 @@ +/* + * This code exists for the sole purpose to serve as another implementation + * of the "private" password hashing method implemened in PasswordHash.php + * and thus to confirm that these password hashes are indeed calculated as + * intended. + * + * Other uses of this code are discouraged. There are much better password + * hashing algorithms available to C programmers; one of those is bcrypt: + * + * http://www.openwall.com/crypt/ + * + * Written by Solar Designer in 2005 and placed in + * the public domain. + * + * There's absolutely no warranty. + */ + +#include +#include + +#ifdef TEST +#include +#endif + +static char *itoa64 = + "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +static void encode64(char *dst, char *src, int count) +{ + int i, value; + + i = 0; + do { + value = (unsigned char)src[i++]; + *dst++ = itoa64[value & 0x3f]; + if (i < count) + value |= (unsigned char)src[i] << 8; + *dst++ = itoa64[(value >> 6) & 0x3f]; + if (i++ >= count) + break; + if (i < count) + value |= (unsigned char)src[i] << 16; + *dst++ = itoa64[(value >> 12) & 0x3f]; + if (i++ >= count) + break; + *dst++ = itoa64[(value >> 18) & 0x3f]; + } while (i < count); +} + +char *crypt_private(char *password, char *setting) +{ + static char output[32]; + MD5_CTX ctx; + char hash[MD5_DIGEST_LENGTH]; + char *p, *salt; + int count_log2, length, count; + + strcpy(output, "*0"); + if (!strncmp(setting, output, 2)) + output[1] = '1'; + + if (strncmp(setting, "$P$", 3)) + return output; + + p = strchr(itoa64, setting[3]); + if (!p) + return output; + count_log2 = p - itoa64; + if (count_log2 < 7 || count_log2 > 31) + return output; + + salt = setting + 4; + if (strlen(salt) < 8) + return output; + + length = strlen(password); + + MD5_Init(&ctx); + MD5_Update(&ctx, salt, 8); + MD5_Update(&ctx, password, length); + MD5_Final(hash, &ctx); + + count = 1 << count_log2; + do { + MD5_Init(&ctx); + MD5_Update(&ctx, hash, MD5_DIGEST_LENGTH); + MD5_Update(&ctx, password, length); + MD5_Final(hash, &ctx); + } while (--count); + + memcpy(output, setting, 12); + encode64(&output[12], hash, MD5_DIGEST_LENGTH); + + return output; +} + +#ifdef TEST +int main(int argc, char **argv) +{ + if (argc != 3) return 1; + + puts(crypt_private(argv[1], argv[2])); + + return 0; +} +#endif diff --git a/application/libraries/phpass-0.1/test.php b/application/libraries/phpass-0.1/test.php new file mode 100644 index 0000000..36ef653 --- /dev/null +++ b/application/libraries/phpass-0.1/test.php @@ -0,0 +1,72 @@ +HashPassword($correct); + +print "Hash: " . $hash . "\n"; + +$check = $t_hasher->CheckPassword($correct, $hash); +if ($check) $ok++; +print "Check correct: '" . $check . "' (should be '1')\n"; + +$wrong = "test12346"; +$check = $t_hasher->CheckPassword($wrong, $hash); +if (!$check) $ok++; +print "Check wrong: '" . $check . "' (should be '0' or '')\n"; + +unset($t_hasher); + +# Force the use of weaker portable hashes. +$t_hasher = new PasswordHash(8, TRUE); + +$hash = $t_hasher->HashPassword($correct); + +print "Hash: " . $hash . "\n"; + +$check = $t_hasher->CheckPassword($correct, $hash); +if ($check) $ok++; +print "Check correct: '" . $check . "' (should be '1')\n"; + +$check = $t_hasher->CheckPassword($wrong, $hash); +if (!$check) $ok++; +print "Check wrong: '" . $check . "' (should be '0' or '')\n"; + +# A correct portable hash for "test12345". +# Please note the use of single quotes to ensure that the dollar signs will +# be interpreted literally. Of course, a real application making use of the +# framework won't store password hashes within a PHP source file anyway. +# We only do this for testing. +$hash = '$P$9IQRaTwmfeRo7ud9Fh4E2PdI0S3r.L0'; + +print "Hash: " . $hash . "\n"; + +$check = $t_hasher->CheckPassword($correct, $hash); +if ($check) $ok++; +print "Check correct: '" . $check . "' (should be '1')\n"; + +$check = $t_hasher->CheckPassword($wrong, $hash); +if (!$check) $ok++; +print "Check wrong: '" . $check . "' (should be '0' or '')\n"; + +if ($ok == 6) + print "All tests have PASSED\n"; +else + print "Some tests have FAILED\n"; + +?> diff --git a/application/libraries/phpass/PasswordHash.php b/application/libraries/phpass/PasswordHash.php new file mode 100644 index 0000000..12958c7 --- /dev/null +++ b/application/libraries/phpass/PasswordHash.php @@ -0,0 +1,253 @@ + in 2004-2006 and placed in +# the public domain. Revised in subsequent years, still public domain. +# +# There's absolutely no warranty. +# +# The homepage URL for this framework is: +# +# http://www.openwall.com/phpass/ +# +# Please be sure to update the Version line if you edit this file in any way. +# It is suggested that you leave the main version number intact, but indicate +# your project name (after the slash) and add your own revision information. +# +# Please do not change the "private" password hashing method implemented in +# here, thereby making your hashes incompatible. However, if you must, please +# change the hash type identifier (the "$P$") to something different. +# +# Obviously, since this code is in the public domain, the above are not +# requirements (there can be none), but merely suggestions. +# +class PasswordHash { + var $itoa64; + var $iteration_count_log2; + var $portable_hashes; + var $random_state; + + function PasswordHash($iteration_count_log2, $portable_hashes) + { + $this->itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; + + if ($iteration_count_log2 < 4 || $iteration_count_log2 > 31) + $iteration_count_log2 = 8; + $this->iteration_count_log2 = $iteration_count_log2; + + $this->portable_hashes = $portable_hashes; + + $this->random_state = microtime(); + if (function_exists('getmypid')) + $this->random_state .= getmypid(); + } + + function get_random_bytes($count) + { + $output = ''; + if (is_readable('/dev/urandom') && + ($fh = @fopen('/dev/urandom', 'rb'))) { + $output = fread($fh, $count); + fclose($fh); + } + + if (strlen($output) < $count) { + $output = ''; + for ($i = 0; $i < $count; $i += 16) { + $this->random_state = + md5(microtime() . $this->random_state); + $output .= + pack('H*', md5($this->random_state)); + } + $output = substr($output, 0, $count); + } + + return $output; + } + + function encode64($input, $count) + { + $output = ''; + $i = 0; + do { + $value = ord($input[$i++]); + $output .= $this->itoa64[$value & 0x3f]; + if ($i < $count) + $value |= ord($input[$i]) << 8; + $output .= $this->itoa64[($value >> 6) & 0x3f]; + if ($i++ >= $count) + break; + if ($i < $count) + $value |= ord($input[$i]) << 16; + $output .= $this->itoa64[($value >> 12) & 0x3f]; + if ($i++ >= $count) + break; + $output .= $this->itoa64[($value >> 18) & 0x3f]; + } while ($i < $count); + + return $output; + } + + function gensalt_private($input) + { + $output = '$P$'; + $output .= $this->itoa64[min($this->iteration_count_log2 + + ((PHP_VERSION >= '5') ? 5 : 3), 30)]; + $output .= $this->encode64($input, 6); + + return $output; + } + + function crypt_private($password, $setting) + { + $output = '*0'; + if (substr($setting, 0, 2) == $output) + $output = '*1'; + + $id = substr($setting, 0, 3); + # We use "$P$", phpBB3 uses "$H$" for the same thing + if ($id != '$P$' && $id != '$H$') + return $output; + + $count_log2 = strpos($this->itoa64, $setting[3]); + if ($count_log2 < 7 || $count_log2 > 30) + return $output; + + $count = 1 << $count_log2; + + $salt = substr($setting, 4, 8); + if (strlen($salt) != 8) + return $output; + + # We're kind of forced to use MD5 here since it's the only + # cryptographic primitive available in all versions of PHP + # currently in use. To implement our own low-level crypto + # in PHP would result in much worse performance and + # consequently in lower iteration counts and hashes that are + # quicker to crack (by non-PHP code). + if (PHP_VERSION >= '5') { + $hash = md5($salt . $password, TRUE); + do { + $hash = md5($hash . $password, TRUE); + } while (--$count); + } else { + $hash = pack('H*', md5($salt . $password)); + do { + $hash = pack('H*', md5($hash . $password)); + } while (--$count); + } + + $output = substr($setting, 0, 12); + $output .= $this->encode64($hash, 16); + + return $output; + } + + function gensalt_extended($input) + { + $count_log2 = min($this->iteration_count_log2 + 8, 24); + # This should be odd to not reveal weak DES keys, and the + # maximum valid value is (2**24 - 1) which is odd anyway. + $count = (1 << $count_log2) - 1; + + $output = '_'; + $output .= $this->itoa64[$count & 0x3f]; + $output .= $this->itoa64[($count >> 6) & 0x3f]; + $output .= $this->itoa64[($count >> 12) & 0x3f]; + $output .= $this->itoa64[($count >> 18) & 0x3f]; + + $output .= $this->encode64($input, 3); + + return $output; + } + + function gensalt_blowfish($input) + { + # This one needs to use a different order of characters and a + # different encoding scheme from the one in encode64() above. + # We care because the last character in our encoded string will + # only represent 2 bits. While two known implementations of + # bcrypt will happily accept and correct a salt string which + # has the 4 unused bits set to non-zero, we do not want to take + # chances and we also do not want to waste an additional byte + # of entropy. + $itoa64 = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + + $output = '$2a$'; + $output .= chr(ord('0') + $this->iteration_count_log2 / 10); + $output .= chr(ord('0') + $this->iteration_count_log2 % 10); + $output .= '$'; + + $i = 0; + do { + $c1 = ord($input[$i++]); + $output .= $itoa64[$c1 >> 2]; + $c1 = ($c1 & 0x03) << 4; + if ($i >= 16) { + $output .= $itoa64[$c1]; + break; + } + + $c2 = ord($input[$i++]); + $c1 |= $c2 >> 4; + $output .= $itoa64[$c1]; + $c1 = ($c2 & 0x0f) << 2; + + $c2 = ord($input[$i++]); + $c1 |= $c2 >> 6; + $output .= $itoa64[$c1]; + $output .= $itoa64[$c2 & 0x3f]; + } while (1); + + return $output; + } + + function HashPassword($password) + { + $random = ''; + + if (CRYPT_BLOWFISH == 1 && !$this->portable_hashes) { + $random = $this->get_random_bytes(16); + $hash = + crypt($password, $this->gensalt_blowfish($random)); + if (strlen($hash) == 60) + return $hash; + } + + if (CRYPT_EXT_DES == 1 && !$this->portable_hashes) { + if (strlen($random) < 3) + $random = $this->get_random_bytes(3); + $hash = + crypt($password, $this->gensalt_extended($random)); + if (strlen($hash) == 20) + return $hash; + } + + if (strlen($random) < 6) + $random = $this->get_random_bytes(6); + $hash = + $this->crypt_private($password, + $this->gensalt_private($random)); + if (strlen($hash) == 34) + return $hash; + + # Returning '*' on error is safe here, but would _not_ be safe + # in a crypt(3)-like function used _both_ for generating new + # hashes and for validating passwords against existing hashes. + return '*'; + } + + function CheckPassword($password, $stored_hash) + { + $hash = $this->crypt_private($password, $stored_hash); + if ($hash[0] == '*') + $hash = crypt($password, $stored_hash); + + return $hash == $stored_hash; + } +} + +?> diff --git a/application/libraries/phpass/c/Makefile b/application/libraries/phpass/c/Makefile new file mode 100644 index 0000000..fe48917 --- /dev/null +++ b/application/libraries/phpass/c/Makefile @@ -0,0 +1,21 @@ +# +# Written by Solar Designer and placed in the public domain. +# See crypt_private.c for more information. +# +CC = gcc +LD = $(CC) +RM = rm -f +CFLAGS = -Wall -O2 -fomit-frame-pointer -funroll-loops +LDFLAGS = -s +LIBS = -lcrypto + +all: crypt_private-test + +crypt_private-test: crypt_private-test.o + $(LD) $(LDFLAGS) $(LIBS) crypt_private-test.o -o $@ + +crypt_private-test.o: crypt_private.c + $(CC) -c $(CFLAGS) crypt_private.c -DTEST -o $@ + +clean: + $(RM) crypt_private-test* diff --git a/application/libraries/phpass/c/crypt_private.c b/application/libraries/phpass/c/crypt_private.c new file mode 100644 index 0000000..6abc05b --- /dev/null +++ b/application/libraries/phpass/c/crypt_private.c @@ -0,0 +1,106 @@ +/* + * This code exists for the sole purpose to serve as another implementation + * of the "private" password hashing method implemened in PasswordHash.php + * and thus to confirm that these password hashes are indeed calculated as + * intended. + * + * Other uses of this code are discouraged. There are much better password + * hashing algorithms available to C programmers; one of those is bcrypt: + * + * http://www.openwall.com/crypt/ + * + * Written by Solar Designer in 2005 and placed in + * the public domain. + * + * There's absolutely no warranty. + */ + +#include +#include + +#ifdef TEST +#include +#endif + +static char *itoa64 = + "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + +static void encode64(char *dst, char *src, int count) +{ + int i, value; + + i = 0; + do { + value = (unsigned char)src[i++]; + *dst++ = itoa64[value & 0x3f]; + if (i < count) + value |= (unsigned char)src[i] << 8; + *dst++ = itoa64[(value >> 6) & 0x3f]; + if (i++ >= count) + break; + if (i < count) + value |= (unsigned char)src[i] << 16; + *dst++ = itoa64[(value >> 12) & 0x3f]; + if (i++ >= count) + break; + *dst++ = itoa64[(value >> 18) & 0x3f]; + } while (i < count); +} + +char *crypt_private(char *password, char *setting) +{ + static char output[35]; + MD5_CTX ctx; + char hash[MD5_DIGEST_LENGTH]; + char *p, *salt; + int count_log2, length, count; + + strcpy(output, "*0"); + if (!strncmp(setting, output, 2)) + output[1] = '1'; + + if (strncmp(setting, "$P$", 3)) + return output; + + p = strchr(itoa64, setting[3]); + if (!p) + return output; + count_log2 = p - itoa64; + if (count_log2 < 7 || count_log2 > 30) + return output; + + salt = setting + 4; + if (strlen(salt) < 8) + return output; + + length = strlen(password); + + MD5_Init(&ctx); + MD5_Update(&ctx, salt, 8); + MD5_Update(&ctx, password, length); + MD5_Final(hash, &ctx); + + count = 1 << count_log2; + do { + MD5_Init(&ctx); + MD5_Update(&ctx, hash, MD5_DIGEST_LENGTH); + MD5_Update(&ctx, password, length); + MD5_Final(hash, &ctx); + } while (--count); + + memcpy(output, setting, 12); + encode64(&output[12], hash, MD5_DIGEST_LENGTH); + + return output; +} + +#ifdef TEST +int main(int argc, char **argv) +{ + if (argc != 3) return 1; + + puts(crypt_private(argv[1], argv[2])); + + return 0; +} +#endif diff --git a/application/libraries/phpass/test.php b/application/libraries/phpass/test.php new file mode 100644 index 0000000..2f4a41c --- /dev/null +++ b/application/libraries/phpass/test.php @@ -0,0 +1,72 @@ +HashPassword($correct); + +print 'Hash: ' . $hash . "\n"; + +$check = $t_hasher->CheckPassword($correct, $hash); +if ($check) $ok++; +print "Check correct: '" . $check . "' (should be '1')\n"; + +$wrong = 'test12346'; +$check = $t_hasher->CheckPassword($wrong, $hash); +if (!$check) $ok++; +print "Check wrong: '" . $check . "' (should be '0' or '')\n"; + +unset($t_hasher); + +# Force the use of weaker portable hashes. +$t_hasher = new PasswordHash(8, TRUE); + +$hash = $t_hasher->HashPassword($correct); + +print 'Hash: ' . $hash . "\n"; + +$check = $t_hasher->CheckPassword($correct, $hash); +if ($check) $ok++; +print "Check correct: '" . $check . "' (should be '1')\n"; + +$check = $t_hasher->CheckPassword($wrong, $hash); +if (!$check) $ok++; +print "Check wrong: '" . $check . "' (should be '0' or '')\n"; + +# A correct portable hash for 'test12345'. +# Please note the use of single quotes to ensure that the dollar signs will +# be interpreted literally. Of course, a real application making use of the +# framework won't store password hashes within a PHP source file anyway. +# We only do this for testing. +$hash = '$P$9IQRaTwmfeRo7ud9Fh4E2PdI0S3r.L0'; + +print 'Hash: ' . $hash . "\n"; + +$check = $t_hasher->CheckPassword($correct, $hash); +if ($check) $ok++; +print "Check correct: '" . $check . "' (should be '1')\n"; + +$check = $t_hasher->CheckPassword($wrong, $hash); +if (!$check) $ok++; +print "Check wrong: '" . $check . "' (should be '0' or '')\n"; + +if ($ok == 6) + print "All tests have PASSED\n"; +else + print "Some tests have FAILED\n"; + +?> diff --git a/application/logs/1529b2ff43714ade215afb3dd3e5f4f3 b/application/logs/1529b2ff43714ade215afb3dd3e5f4f3 new file mode 100644 index 0000000..e69de29 diff --git a/application/logs/844d6d932936f5e50641db3bfbd38137 b/application/logs/844d6d932936f5e50641db3bfbd38137 new file mode 100644 index 0000000..e69de29 diff --git a/application/migrations/001_install_ion_auth.php b/application/migrations/001_install_ion_auth.php new file mode 100644 index 0000000..cf4496e --- /dev/null +++ b/application/migrations/001_install_ion_auth.php @@ -0,0 +1,248 @@ +load->dbforge(); + + $this->load->config('ion_auth', TRUE); + $this->tables = $this->config->item('tables', 'ion_auth'); + } + + public function up() { + // Drop table 'groups' if it exists + $this->dbforge->drop_table($this->tables['groups'], TRUE); + + // Table structure for table 'groups' + $this->dbforge->add_field(array( + 'id' => array( + 'type' => 'MEDIUMINT', + 'constraint' => '8', + 'unsigned' => TRUE, + 'auto_increment' => TRUE + ), + 'name' => array( + 'type' => 'VARCHAR', + 'constraint' => '20', + ), + 'description' => array( + 'type' => 'VARCHAR', + 'constraint' => '100', + ) + )); + $this->dbforge->add_key('id', TRUE); + $this->dbforge->create_table($this->tables['groups']); + + // Dumping data for table 'groups' + $data = array( + array( + 'id' => '1', + 'name' => 'admin', + 'description' => 'Administrator' + ), + array( + 'id' => '2', + 'name' => 'members', + 'description' => 'General User' + ) + ); + $this->db->insert_batch($this->tables['groups'], $data); + + // Drop table 'users' if it exists + $this->dbforge->drop_table($this->tables['users'], TRUE); + + // Table structure for table 'users' + $this->dbforge->add_field(array( + 'id' => array( + 'type' => 'MEDIUMINT', + 'constraint' => '8', + 'unsigned' => TRUE, + 'auto_increment' => TRUE + ), + 'ip_address' => array( + 'type' => 'VARCHAR', + 'constraint' => '45' + ), + 'username' => array( + 'type' => 'VARCHAR', + 'constraint' => '100', + ), + 'password' => array( + 'type' => 'VARCHAR', + 'constraint' => '80', + ), + 'salt' => array( + 'type' => 'VARCHAR', + 'constraint' => '40', + 'null' => TRUE + ), + 'email' => array( + 'type' => 'VARCHAR', + 'constraint' => '254' + ), + 'activation_code' => array( + 'type' => 'VARCHAR', + 'constraint' => '40', + 'null' => TRUE + ), + 'forgotten_password_code' => array( + 'type' => 'VARCHAR', + 'constraint' => '40', + 'null' => TRUE + ), + 'forgotten_password_time' => array( + 'type' => 'INT', + 'constraint' => '11', + 'unsigned' => TRUE, + 'null' => TRUE + ), + 'remember_code' => array( + 'type' => 'VARCHAR', + 'constraint' => '40', + 'null' => TRUE + ), + 'created_on' => array( + 'type' => 'INT', + 'constraint' => '11', + 'unsigned' => TRUE, + ), + 'last_login' => array( + 'type' => 'INT', + 'constraint' => '11', + 'unsigned' => TRUE, + 'null' => TRUE + ), + 'active' => array( + 'type' => 'TINYINT', + 'constraint' => '1', + 'unsigned' => TRUE, + 'null' => TRUE + ), + 'first_name' => array( + 'type' => 'VARCHAR', + 'constraint' => '50', + 'null' => TRUE + ), + 'last_name' => array( + 'type' => 'VARCHAR', + 'constraint' => '50', + 'null' => TRUE + ), + 'company' => array( + 'type' => 'VARCHAR', + 'constraint' => '100', + 'null' => TRUE + ), + 'phone' => array( + 'type' => 'VARCHAR', + 'constraint' => '20', + 'null' => TRUE + ) + + )); + $this->dbforge->add_key('id', TRUE); + $this->dbforge->create_table($this->tables['users']); + + // Dumping data for table 'users' + $data = array( + 'id' => '1', + 'ip_address' => '127.0.0.1', + 'username' => 'administrator', + 'password' => '$2a$07$SeBknntpZror9uyftVopmu61qg0ms8Qv1yV6FG.kQOSM.9QhmTo36', + 'salt' => '', + 'email' => 'admin@admin.com', + 'activation_code' => '', + 'forgotten_password_code' => NULL, + 'created_on' => '1268889823', + 'last_login' => '1268889823', + 'active' => '1', + 'first_name' => 'Admin', + 'last_name' => 'istrator', + 'company' => 'ADMIN', + 'phone' => '0', + ); + $this->db->insert($this->tables['users'], $data); + + + // Drop table 'users_groups' if it exists + $this->dbforge->drop_table($this->tables['users_groups'], TRUE); + + // Table structure for table 'users_groups' + $this->dbforge->add_field(array( + 'id' => array( + 'type' => 'MEDIUMINT', + 'constraint' => '8', + 'unsigned' => TRUE, + 'auto_increment' => TRUE + ), + 'user_id' => array( + 'type' => 'MEDIUMINT', + 'constraint' => '8', + 'unsigned' => TRUE + ), + 'group_id' => array( + 'type' => 'MEDIUMINT', + 'constraint' => '8', + 'unsigned' => TRUE + ) + )); + $this->dbforge->add_key('id', TRUE); + $this->dbforge->create_table($this->tables['users_groups']); + + // Dumping data for table 'users_groups' + $data = array( + array( + 'id' => '1', + 'user_id' => '1', + 'group_id' => '1', + ), + array( + 'id' => '2', + 'user_id' => '1', + 'group_id' => '2', + ) + ); + $this->db->insert_batch($this->tables['users_groups'], $data); + + + // Drop table 'login_attempts' if it exists + $this->dbforge->drop_table($this->tables['login_attempts'], TRUE); + + // Table structure for table 'login_attempts' + $this->dbforge->add_field(array( + 'id' => array( + 'type' => 'MEDIUMINT', + 'constraint' => '8', + 'unsigned' => TRUE, + 'auto_increment' => TRUE + ), + 'ip_address' => array( + 'type' => 'VARCHAR', + 'constraint' => '45' + ), + 'login' => array( + 'type' => 'VARCHAR', + 'constraint' => '100', + 'null' => TRUE + ), + 'time' => array( + 'type' => 'INT', + 'constraint' => '11', + 'unsigned' => TRUE, + 'null' => TRUE + ) + )); + $this->dbforge->add_key('id', TRUE); + $this->dbforge->create_table($this->tables['login_attempts']); + + } + + public function down() { + $this->dbforge->drop_table($this->tables['users'], TRUE); + $this->dbforge->drop_table($this->tables['groups'], TRUE); + $this->dbforge->drop_table($this->tables['users_groups'], TRUE); + $this->dbforge->drop_table($this->tables['login_attempts'], TRUE); + } +} diff --git a/application/models/Users_model.php b/application/models/Users_model.php new file mode 100644 index 0000000..755f42e --- /dev/null +++ b/application/models/Users_model.php @@ -0,0 +1,31 @@ +load->database(); + } + + public function getAllUsers(){ + $query = $this->db->get('utilisateurs'); + return $query->result(); + } + + public function insert($user){ + $this->db->insert('utilisateurs', $user); + return $this->db->insert_id(); + } + + public function getUser($id){ + $query = $this->db->get_where('utilisateurs',array('id'=>$id)); + return $query->row_array(); + } + + public function activate($data, $id){ + $this->db->where('utilisateurs.id', $id); + return $this->db->update('utilisateurs', $data); + } + +} diff --git a/application/modules/auth/controllers/Auth.php b/application/modules/auth/controllers/Auth.php new file mode 100644 index 0000000..8594de0 --- /dev/null +++ b/application/modules/auth/controllers/Auth.php @@ -0,0 +1,827 @@ +load->database(); + $this->load->library(array('ion_auth','form_validation')); + $this->load->helper(array('url','language')); + $this->load->library('email'); +$this->email->set_newline("\r\n"); + + $this->form_validation->set_error_delimiters($this->config->item('error_start_delimiter', 'ion_auth'), $this->config->item('error_end_delimiter', 'ion_auth')); + + $this->lang->load('auth'); + } + + // redirect if needed, otherwise display the user list + public function index() + { + + if (!$this->ion_auth->logged_in()) + { + // redirect them to the login page + redirect('auth/login', 'refresh'); + } + elseif (!$this->ion_auth->is_admin()) // remove this elseif if you want to enable this for non-admins + { + // redirect them to the home page because they must be an administrator to view this + return show_error('Vous n etes pas un Administrateur pour acceder a cette page.'); + } + else + { + // set the flash data error message if there is one + $this->data['message'] = (validation_errors()) ? validation_errors() : $this->session->flashdata('message'); + + //list the users + $this->data['users'] = $this->ion_auth->users()->result(); + foreach ($this->data['users'] as $k => $user) + { + $this->data['users'][$k]->groups = $this->ion_auth->get_users_groups($user->id)->result(); + } + + $this->_render_page('auth/index', $this->data); + + + + } + } + + // log the user in + public function login() + { + $this->data['title'] = $this->lang->line('login_heading'); + + //validate form input + $this->form_validation->set_rules('identity', str_replace(':', '', $this->lang->line('login_identity_label')), 'required'); + $this->form_validation->set_rules('password', str_replace(':', '', $this->lang->line('login_password_label')), 'required'); + + if ($this->form_validation->run() == true) + { + // check to see if the user is logging in + // check for "remember me" + $remember = (bool) $this->input->post('remember'); + + if ($this->ion_auth->login($this->input->post('identity'), $this->input->post('password'), $remember) && $this->ion_auth->is_admin() || $this->ion_auth->in_group('commercial')) + { + //if the login is successful + //redirect them back to the home page + $this->session->set_flashdata('message', $this->ion_auth->messages()); + redirect('dashboard/home', 'refresh'); + } + else + { + // if the login was un-successful + // redirect them back to the login page + $this->session->set_flashdata('message', $this->ion_auth->errors()); + redirect('auth/login', 'refresh'); // use redirects instead of loading views for compatibility with MY_Controller libraries + } + } + else + { + // the user is not logging in so display the login page + // set the flash data error message if there is one + $this->data['message'] = (validation_errors()) ? validation_errors() : $this->session->flashdata('message'); + + $this->data['identity'] = array('name' => 'identity', + 'id' => 'identity', + 'type' => 'text', + 'value' => $this->form_validation->set_value('identity'), + ); + $this->data['password'] = array('name' => 'password', + 'id' => 'password', + 'type' => 'password', + ); + + $this->_render_page('auth/login', $this->data); + } + } + + // log the user out + public function logout() + { + $this->data['title'] = "Logout"; + + // log the user out + $logout = $this->ion_auth->logout(); + + // redirect them to the login page + $this->session->set_flashdata('message', $this->ion_auth->messages()); + redirect('auth/login', 'refresh'); + } + + // change password + public function change_password() + { + $this->form_validation->set_rules('old', $this->lang->line('change_password_validation_old_password_label'), 'required'); + $this->form_validation->set_rules('new', $this->lang->line('change_password_validation_new_password_label'), 'required|min_length[' . $this->config->item('min_password_length', 'ion_auth') . ']|max_length[' . $this->config->item('max_password_length', 'ion_auth') . ']|matches[new_confirm]'); + $this->form_validation->set_rules('new_confirm', $this->lang->line('change_password_validation_new_password_confirm_label'), 'required'); + + if (!$this->ion_auth->logged_in()) + { + redirect('auth/login', 'refresh'); + } + + $user = $this->ion_auth->user()->row(); + + if ($this->form_validation->run() == false) + { + // display the form + // set the flash data error message if there is one + $this->data['message'] = (validation_errors()) ? validation_errors() : $this->session->flashdata('message'); + + $this->data['min_password_length'] = $this->config->item('min_password_length', 'ion_auth'); + $this->data['old_password'] = array( + 'name' => 'old', + 'id' => 'old', + 'type' => 'password', + ); + $this->data['new_password'] = array( + 'name' => 'new', + 'id' => 'new', + 'type' => 'password', + 'pattern' => '^.{'.$this->data['min_password_length'].'}.*$', + ); + $this->data['new_password_confirm'] = array( + 'name' => 'new_confirm', + 'id' => 'new_confirm', + 'type' => 'password', + 'pattern' => '^.{'.$this->data['min_password_length'].'}.*$', + ); + $this->data['user_id'] = array( + 'name' => 'user_id', + 'id' => 'user_id', + 'type' => 'hidden', + 'value' => $user->id, + ); + + // render + $this->_render_page('auth/change_password', $this->data); + } + else + { + $identity = $this->session->userdata('identity'); + + $change = $this->ion_auth->change_password($identity, $this->input->post('old'), $this->input->post('new')); + + if ($change) + { + //if the password was successfully changed + $this->session->set_flashdata('message', $this->ion_auth->messages()); + $this->logout(); + } + else + { + $this->session->set_flashdata('message', $this->ion_auth->errors()); + redirect('auth/change_password', 'refresh'); + } + } + } + + // forgot password + public function forgot_password() + { + // setting validation rules by checking whether identity is username or email + if($this->config->item('identity', 'ion_auth') != 'email' ) + { + $this->form_validation->set_rules('identity', $this->lang->line('forgot_password_identity_label'), 'required'); + } + else + { + $this->form_validation->set_rules('identity', $this->lang->line('forgot_password_validation_email_label'), 'required|valid_email'); + } + + + if ($this->form_validation->run() == false) + { + $this->data['type'] = $this->config->item('identity','ion_auth'); + // setup the input + $this->data['identity'] = array('name' => 'identity', + 'id' => 'identity', + ); + + if ( $this->config->item('identity', 'ion_auth') != 'email' ){ + $this->data['identity_label'] = $this->lang->line('forgot_password_identity_label'); + } + else + { + $this->data['identity_label'] = $this->lang->line('forgot_password_email_identity_label'); + } + + // set any errors and display the form + $this->data['message'] = (validation_errors()) ? validation_errors() : $this->session->flashdata('message'); + $this->_render_page('auth/forgot_password', $this->data); + } + else + { + $identity_column = $this->config->item('identity','ion_auth'); + $identity = $this->ion_auth->where($identity_column, $this->input->post('identity'))->users()->row(); + + if(empty($identity)) { + + if($this->config->item('identity', 'ion_auth') != 'email') + { + $this->ion_auth->set_error('forgot_password_identity_not_found'); + } + else + { + $this->ion_auth->set_error('forgot_password_email_not_found'); + } + + $this->session->set_flashdata('message', $this->ion_auth->errors()); + redirect("auth/forgot_password", 'refresh'); + } + + // run the forgotten password method to email an activation code to the user + $forgotten = $this->ion_auth->forgotten_password($identity->{$this->config->item('identity', 'ion_auth')}); + + if ($forgotten) + { + // if there were no errors + $this->session->set_flashdata('message', $this->ion_auth->messages()); + redirect("auth/login", 'refresh'); //we should display a confirmation page here instead of the login page + } + else + { + $this->session->set_flashdata('message', $this->ion_auth->errors()); + redirect("auth/forgot_password", 'refresh'); + } + } + } + + // reset password - final step for forgotten password + public function reset_password($code = NULL) + { + if (!$code) + { + show_404(); + } + + $user = $this->ion_auth->forgotten_password_check($code); + + if ($user) + { + // if the code is valid then display the password reset form + + $this->form_validation->set_rules('new', $this->lang->line('reset_password_validation_new_password_label'), 'required|min_length[' . $this->config->item('min_password_length', 'ion_auth') . ']|max_length[' . $this->config->item('max_password_length', 'ion_auth') . ']|matches[new_confirm]'); + $this->form_validation->set_rules('new_confirm', $this->lang->line('reset_password_validation_new_password_confirm_label'), 'required'); + + if ($this->form_validation->run() == false) + { + // display the form + + // set the flash data error message if there is one + $this->data['message'] = (validation_errors()) ? validation_errors() : $this->session->flashdata('message'); + + $this->data['min_password_length'] = $this->config->item('min_password_length', 'ion_auth'); + $this->data['new_password'] = array( + 'name' => 'new', + 'id' => 'new', + 'type' => 'password', + 'pattern' => '^.{'.$this->data['min_password_length'].'}.*$', + ); + $this->data['new_password_confirm'] = array( + 'name' => 'new_confirm', + 'id' => 'new_confirm', + 'type' => 'password', + 'pattern' => '^.{'.$this->data['min_password_length'].'}.*$', + ); + $this->data['user_id'] = array( + 'name' => 'user_id', + 'id' => 'user_id', + 'type' => 'hidden', + 'value' => $user->id, + ); + $this->data['csrf'] = $this->_get_csrf_nonce(); + $this->data['code'] = $code; + + // render + $this->_render_page('auth/reset_password', $this->data); + } + else + { + // do we have a valid request? + if ($this->_valid_csrf_nonce() === FALSE || $user->id != $this->input->post('user_id')) + { + + // something fishy might be up + $this->ion_auth->clear_forgotten_password_code($code); + + show_error($this->lang->line('error_csrf')); + + } + else + { + // finally change the password + $identity = $user->{$this->config->item('identity', 'ion_auth')}; + + $change = $this->ion_auth->reset_password($identity, $this->input->post('new')); + + if ($change) + { + // if the password was successfully changed + $this->session->set_flashdata('message', $this->ion_auth->messages()); + redirect("auth/login", 'refresh'); + } + else + { + $this->session->set_flashdata('message', $this->ion_auth->errors()); + redirect('auth/reset_password/' . $code, 'refresh'); + } + } + } + } + else + { + // if the code is invalid then send them back to the forgot password page + $this->session->set_flashdata('message', $this->ion_auth->errors()); + redirect("auth/forgot_password", 'refresh'); + } + } + + + // activate the user + public function activate($id, $code=false) + { + if ($code !== false) + { + $activation = $this->ion_auth->activate($id, $code); + } + else if ($this->ion_auth->is_admin()) + { + $activation = $this->ion_auth->activate($id); + } + + if ($activation) + { + // redirect them to the auth page + $this->session->set_flashdata('message', $this->ion_auth->messages()); + redirect("auth", 'refresh'); + } + else + { + // redirect them to the forgot password page + $this->session->set_flashdata('message', $this->ion_auth->errors()); + redirect("auth/forgot_password", 'refresh'); + } + } + + // deactivate the user + public function deactivate($id = NULL) + { + if (!$this->ion_auth->logged_in() || !$this->ion_auth->is_admin()) + { + // redirect them to the home page because they must be an administrator to view this + return show_error('Il faut que vous etes un administrateur pour accédera cette pages.'); + } + + $id = (int) $id; + + $this->load->library('form_validation'); + $this->form_validation->set_rules('confirm', $this->lang->line('deactivate_validation_confirm_label'), 'required'); + $this->form_validation->set_rules('id', $this->lang->line('deactivate_validation_user_id_label'), 'required|alpha_numeric'); + + if ($this->form_validation->run() == FALSE) + { + // insert csrf check + $this->data['csrf'] = $this->_get_csrf_nonce(); + $this->data['user'] = $this->ion_auth->user($id)->row(); + + $this->_render_page('auth/deactivate_user', $this->data); + } + else + { + // do we really want to deactivate? + if ($this->input->post('confirm') == 'yes') + { + // do we have a valid request? + if ($this->_valid_csrf_nonce() === FALSE || $id != $this->input->post('id')) + { + show_error($this->lang->line('error_csrf')); + } + + // do we have the right userlevel? + if ($this->ion_auth->logged_in() && $this->ion_auth->is_admin()) + { + $this->ion_auth->deactivate($id); + } + } + + // redirect them back to the auth page + redirect('auth', 'refresh'); + } + } + + // create a new user + public function create_user() + { + $this->data['title'] = $this->lang->line('create_user_heading'); + + if (!$this->ion_auth->logged_in() || !$this->ion_auth->is_admin()) + { + redirect('auth', 'refresh'); + } + + $tables = $this->config->item('tables','ion_auth'); + $identity_column = $this->config->item('identity','ion_auth'); + $this->data['identity_column'] = $identity_column; + + // validate form input + $this->form_validation->set_rules('first_name', $this->lang->line('create_user_validation_fname_label'), 'required'); + $this->form_validation->set_rules('last_name', $this->lang->line('create_user_validation_lname_label'), 'required'); + if($identity_column!=='email') + { + $this->form_validation->set_rules('identity',$this->lang->line('create_user_validation_identity_label'),'required|is_unique['.$tables['users'].'.'.$identity_column.']'); + $this->form_validation->set_rules('email', $this->lang->line('create_user_validation_email_label'), 'required|valid_email'); + } + else + { + $this->form_validation->set_rules('email', $this->lang->line('create_user_validation_email_label'), 'required|valid_email|is_unique[' . $tables['users'] . '.email]'); + } + $this->form_validation->set_rules('phone', $this->lang->line('create_user_validation_phone_label'), 'trim'); + $this->form_validation->set_rules('company', $this->lang->line('create_user_validation_company_label'), 'trim'); + $this->form_validation->set_rules('password', $this->lang->line('create_user_validation_password_label'), 'required|min_length[' . $this->config->item('min_password_length', 'ion_auth') . ']|max_length[' . $this->config->item('max_password_length', 'ion_auth') . ']|matches[password_confirm]'); + $this->form_validation->set_rules('password_confirm', $this->lang->line('create_user_validation_password_confirm_label'), 'required'); + + if ($this->form_validation->run() == true) + { + $email = strtolower($this->input->post('email')); + $identity = ($identity_column==='email') ? $email : $this->input->post('identity'); + $password = $this->input->post('password'); + + $additional_data = array( + 'first_name' => $this->input->post('first_name'), + 'last_name' => $this->input->post('last_name'), + 'company' => $this->input->post('company'), + 'phone' => $this->input->post('phone'), + ); + } + if ($this->form_validation->run() == true && $this->ion_auth->register($identity, $password, $email, $additional_data)) + { + // check to see if we are creating the user + // redirect them back to the admin page + $this->session->set_flashdata('message', $this->ion_auth->messages()); + redirect("auth", 'refresh'); + } + else + { + // display the create user form + // set the flash data error message if there is one + $this->data['message'] = (validation_errors() ? validation_errors() : ($this->ion_auth->errors() ? $this->ion_auth->errors() : $this->session->flashdata('message'))); + + $this->data['first_name'] = array( + 'name' => 'first_name', + 'id' => 'first_name', + 'type' => 'text', + 'value' => $this->form_validation->set_value('first_name'), + ); + $this->data['last_name'] = array( + 'name' => 'last_name', + 'id' => 'last_name', + 'type' => 'text', + 'value' => $this->form_validation->set_value('last_name'), + ); + $this->data['identity'] = array( + 'name' => 'identity', + 'id' => 'identity', + 'type' => 'text', + 'value' => $this->form_validation->set_value('identity'), + ); + $this->data['email'] = array( + 'name' => 'email', + 'id' => 'email', + 'type' => 'text', + 'value' => $this->form_validation->set_value('email'), + ); + $this->data['company'] = array( + 'name' => 'company', + 'id' => 'company', + 'type' => 'text', + 'value' => $this->form_validation->set_value('company'), + ); + $this->data['phone'] = array( + 'name' => 'phone', + 'id' => 'phone', + 'type' => 'text', + 'value' => $this->form_validation->set_value('phone'), + ); + $this->data['password'] = array( + 'name' => 'password', + 'id' => 'password', + 'type' => 'password', + 'value' => $this->form_validation->set_value('password'), + ); + $this->data['password_confirm'] = array( + 'name' => 'password_confirm', + 'id' => 'password_confirm', + 'type' => 'password', + 'value' => $this->form_validation->set_value('password_confirm'), + ); + + $this->_render_page('auth/create_user', $this->data); + } + } + + // edit a user + public function edit_user($id) + { + $this->data['title'] = $this->lang->line('edit_user_heading'); + + if (!$this->ion_auth->logged_in() || (!$this->ion_auth->is_admin() && !($this->ion_auth->user()->row()->id == $id))) + { + redirect('auth', 'refresh'); + } + + $user = $this->ion_auth->user($id)->row(); + $groups=$this->ion_auth->groups()->result_array(); + $currentGroups = $this->ion_auth->get_users_groups($id)->result(); + + // validate form input + $this->form_validation->set_rules('first_name', $this->lang->line('edit_user_validation_fname_label'), 'required'); + $this->form_validation->set_rules('last_name', $this->lang->line('edit_user_validation_lname_label'), 'required'); + $this->form_validation->set_rules('phone', $this->lang->line('edit_user_validation_phone_label'), 'required'); + $this->form_validation->set_rules('company', $this->lang->line('edit_user_validation_company_label'), 'required'); + + if (isset($_POST) && !empty($_POST)) + { + // do we have a valid request? + if ($this->_valid_csrf_nonce() === FALSE || $id != $this->input->post('id')) + { + show_error($this->lang->line('error_csrf')); + } + + // update the password if it was posted + if ($this->input->post('password')) + { + $this->form_validation->set_rules('password', $this->lang->line('edit_user_validation_password_label'), 'required|min_length[' . $this->config->item('min_password_length', 'ion_auth') . ']|max_length[' . $this->config->item('max_password_length', 'ion_auth') . ']|matches[password_confirm]'); + $this->form_validation->set_rules('password_confirm', $this->lang->line('edit_user_validation_password_confirm_label'), 'required'); + } + + if ($this->form_validation->run() === TRUE) + { + $data = array( + 'first_name' => $this->input->post('first_name'), + 'last_name' => $this->input->post('last_name'), + 'company' => $this->input->post('company'), + 'phone' => $this->input->post('phone'), + ); + + // update the password if it was posted + if ($this->input->post('password')) + { + $data['password'] = $this->input->post('password'); + } + + + + // Only allow updating groups if user is admin + if ($this->ion_auth->is_admin()) + { + //Update the groups user belongs to + $groupData = $this->input->post('groups'); + + if (isset($groupData) && !empty($groupData)) { + + $this->ion_auth->remove_from_group('', $id); + + foreach ($groupData as $grp) { + $this->ion_auth->add_to_group($grp, $id); + } + + } + } + + // check to see if we are updating the user + if($this->ion_auth->update($user->id, $data)) + { + // redirect them back to the admin page if admin, or to the base url if non admin + $this->session->set_flashdata('message', $this->ion_auth->messages() ); + if ($this->ion_auth->is_admin()) + { + redirect('auth', 'refresh'); + } + else + { + redirect('/', 'refresh'); + } + + } + else + { + // redirect them back to the admin page if admin, or to the base url if non admin + $this->session->set_flashdata('message', $this->ion_auth->errors() ); + if ($this->ion_auth->is_admin()) + { + redirect('auth', 'refresh'); + } + else + { + redirect('/', 'refresh'); + } + + } + + } + } + + // display the edit user form + $this->data['csrf'] = $this->_get_csrf_nonce(); + + // set the flash data error message if there is one + $this->data['message'] = (validation_errors() ? validation_errors() : ($this->ion_auth->errors() ? $this->ion_auth->errors() : $this->session->flashdata('message'))); + + // pass the user to the view + $this->data['user'] = $user; + $this->data['groups'] = $groups; + $this->data['currentGroups'] = $currentGroups; + + $this->data['first_name'] = array( + 'name' => 'first_name', + 'id' => 'first_name', + 'type' => 'text', + 'value' => $this->form_validation->set_value('first_name', $user->first_name), + ); + $this->data['last_name'] = array( + 'name' => 'last_name', + 'id' => 'last_name', + 'type' => 'text', + 'value' => $this->form_validation->set_value('last_name', $user->last_name), + ); + $this->data['company'] = array( + 'name' => 'company', + 'id' => 'company', + 'type' => 'text', + 'value' => $this->form_validation->set_value('company', $user->company), + ); + $this->data['phone'] = array( + 'name' => 'phone', + 'id' => 'phone', + 'type' => 'text', + 'value' => $this->form_validation->set_value('phone', $user->phone), + ); + $this->data['password'] = array( + 'name' => 'password', + 'id' => 'password', + 'type' => 'password' + ); + $this->data['password_confirm'] = array( + 'name' => 'password_confirm', + 'id' => 'password_confirm', + 'type' => 'password' + ); + + $this->_render_page('auth/edit_user', $this->data); + } + + // create a new group + public function create_group() + { + $this->data['title'] = $this->lang->line('create_group_title'); + + if (!$this->ion_auth->logged_in() || !$this->ion_auth->is_admin()) + { + redirect('auth', 'refresh'); + } + + // validate form input + $this->form_validation->set_rules('group_name', $this->lang->line('create_group_validation_name_label'), 'required|alpha_dash'); + + if ($this->form_validation->run() == TRUE) + { + $new_group_id = $this->ion_auth->create_group($this->input->post('group_name'), $this->input->post('description')); + if($new_group_id) + { + // check to see if we are creating the group + // redirect them back to the admin page + $this->session->set_flashdata('message', $this->ion_auth->messages()); + redirect("auth", 'refresh'); + } + } + else + { + // display the create group form + // set the flash data error message if there is one + $this->data['message'] = (validation_errors() ? validation_errors() : ($this->ion_auth->errors() ? $this->ion_auth->errors() : $this->session->flashdata('message'))); + + $this->data['group_name'] = array( + 'name' => 'group_name', + 'id' => 'group_name', + 'type' => 'text', + 'value' => $this->form_validation->set_value('group_name'), + ); + $this->data['description'] = array( + 'name' => 'description', + 'id' => 'description', + 'type' => 'text', + 'value' => $this->form_validation->set_value('description'), + ); + + $this->_render_page('auth/create_group', $this->data); + } + } + + // edit a group + public function edit_group($id) + { + // bail if no group id given + if(!$id || empty($id)) + { + redirect('auth', 'refresh'); + } + + $this->data['title'] = $this->lang->line('edit_group_title'); + + if (!$this->ion_auth->logged_in() || !$this->ion_auth->is_admin()) + { + redirect('auth', 'refresh'); + } + + $group = $this->ion_auth->group($id)->row(); + + // validate form input + $this->form_validation->set_rules('group_name', $this->lang->line('edit_group_validation_name_label'), 'required|alpha_dash'); + + if (isset($_POST) && !empty($_POST)) + { + if ($this->form_validation->run() === TRUE) + { + $group_update = $this->ion_auth->update_group($id, $_POST['group_name'], $_POST['group_description']); + + if($group_update) + { + $this->session->set_flashdata('message', $this->lang->line('edit_group_saved')); + } + else + { + $this->session->set_flashdata('message', $this->ion_auth->errors()); + } + redirect("auth", 'refresh'); + } + } + + // set the flash data error message if there is one + $this->data['message'] = (validation_errors() ? validation_errors() : ($this->ion_auth->errors() ? $this->ion_auth->errors() : $this->session->flashdata('message'))); + + // pass the user to the view + $this->data['group'] = $group; + + $readonly = $this->config->item('admin_group', 'ion_auth') === $group->name ? 'readonly' : ''; + + $this->data['group_name'] = array( + 'name' => 'group_name', + 'id' => 'group_name', + 'type' => 'text', + 'value' => $this->form_validation->set_value('group_name', $group->name), + $readonly => $readonly, + ); + $this->data['group_description'] = array( + 'name' => 'group_description', + 'id' => 'group_description', + 'type' => 'text', + 'value' => $this->form_validation->set_value('group_description', $group->description), + ); + + $this->_render_page('auth/edit_group', $this->data); + } + + + public function _get_csrf_nonce() + { + $this->load->helper('string'); + $key = random_string('alnum', 8); + $value = random_string('alnum', 20); + $this->session->set_flashdata('csrfkey', $key); + $this->session->set_flashdata('csrfvalue', $value); + + return array($key => $value); + } + + public function _valid_csrf_nonce() + { + $csrfkey = $this->input->post($this->session->flashdata('csrfkey')); + if ($csrfkey && $csrfkey == $this->session->flashdata('csrfvalue')) + { + return TRUE; + } + else + { + return FALSE; + } + } + + public function _render_page($view, $data=null, $returnhtml=false)//I think this makes more sense + { + + $this->viewdata = (empty($data)) ? $this->data: $data; + + $view_html = $this->load->view($view, $this->viewdata, $returnhtml); + + if ($returnhtml) return $view_html;//This will return html on 3rd argument being true + } + +} diff --git a/application/modules/auth/models/Ion_auth_model.php b/application/modules/auth/models/Ion_auth_model.php new file mode 100644 index 0000000..4fc2d52 --- /dev/null +++ b/application/modules/auth/models/Ion_auth_model.php @@ -0,0 +1,2370 @@ +load->database(); + $this->config->load('ion_auth', TRUE); + $this->load->helper('cookie'); + $this->load->helper('date'); + $this->lang->load('ion_auth'); + + // initialize db tables data + $this->tables = $this->config->item('tables', 'ion_auth'); + + //initialize data + $this->identity_column = $this->config->item('identity', 'ion_auth'); + $this->store_salt = $this->config->item('store_salt', 'ion_auth'); + $this->salt_length = $this->config->item('salt_length', 'ion_auth'); + $this->join = $this->config->item('join', 'ion_auth'); + + + // initialize hash method options (Bcrypt) + $this->hash_method = $this->config->item('hash_method', 'ion_auth'); + $this->default_rounds = $this->config->item('default_rounds', 'ion_auth'); + $this->random_rounds = $this->config->item('random_rounds', 'ion_auth'); + $this->min_rounds = $this->config->item('min_rounds', 'ion_auth'); + $this->max_rounds = $this->config->item('max_rounds', 'ion_auth'); + + + // initialize messages and error + $this->messages = array(); + $this->errors = array(); + $delimiters_source = $this->config->item('delimiters_source', 'ion_auth'); + + // load the error delimeters either from the config file or use what's been supplied to form validation + if ($delimiters_source === 'form_validation') + { + // load in delimiters from form_validation + // to keep this simple we'll load the value using reflection since these properties are protected + $this->load->library('form_validation'); + $form_validation_class = new ReflectionClass("CI_Form_validation"); + + $error_prefix = $form_validation_class->getProperty("_error_prefix"); + $error_prefix->setAccessible(TRUE); + $this->error_start_delimiter = $error_prefix->getValue($this->form_validation); + $this->message_start_delimiter = $this->error_start_delimiter; + + $error_suffix = $form_validation_class->getProperty("_error_suffix"); + $error_suffix->setAccessible(TRUE); + $this->error_end_delimiter = $error_suffix->getValue($this->form_validation); + $this->message_end_delimiter = $this->error_end_delimiter; + } + else + { + // use delimiters from config + $this->message_start_delimiter = $this->config->item('message_start_delimiter', 'ion_auth'); + $this->message_end_delimiter = $this->config->item('message_end_delimiter', 'ion_auth'); + $this->error_start_delimiter = $this->config->item('error_start_delimiter', 'ion_auth'); + $this->error_end_delimiter = $this->config->item('error_end_delimiter', 'ion_auth'); + } + + + // initialize our hooks object + $this->_ion_hooks = new stdClass; + + // load the bcrypt class if needed + if ($this->hash_method == 'bcrypt') { + if ($this->random_rounds) + { + $rand = rand($this->min_rounds,$this->max_rounds); + $params = array('rounds' => $rand); + } + else + { + $params = array('rounds' => $this->default_rounds); + } + + $params['salt_prefix'] = $this->config->item('salt_prefix', 'ion_auth'); + $this->load->library('bcrypt',$params); + } + + $this->trigger_events('model_constructor'); + } + + /** + * Misc functions + * + * Hash password : Hashes the password to be stored in the database. + * Hash password db : This function takes a password and validates it + * against an entry in the users table. + * Salt : Generates a random salt value. + * + * @author Mathew + */ + + /** + * Hashes the password to be stored in the database. + * + * @return void + * @author Mathew + **/ + public function hash_password($password, $salt=false, $use_sha1_override=FALSE) + { + if (empty($password)) + { + return FALSE; + } + + // bcrypt + if ($use_sha1_override === FALSE && $this->hash_method == 'bcrypt') + { + return $this->bcrypt->hash($password); + } + + + if ($this->store_salt && $salt) + { + return sha1($password . $salt); + } + else + { + $salt = $this->salt(); + return $salt . substr(sha1($salt . $password), 0, -$this->salt_length); + } + } + + /** + * This function takes a password and validates it + * against an entry in the users table. + * + * @return void + * @author Mathew + **/ + public function hash_password_db($id, $password, $use_sha1_override=FALSE) + { + if (empty($id) || empty($password)) + { + return FALSE; + } + + $this->trigger_events('extra_where'); + + $query = $this->db->select('password, salt') + ->where('id', $id) + ->limit(1) + ->order_by('id', 'desc') + ->get($this->tables['users']); + + $hash_password_db = $query->row(); + + if ($query->num_rows() !== 1) + { + return FALSE; + } + + // bcrypt + if ($use_sha1_override === FALSE && $this->hash_method == 'bcrypt') + { + if ($this->bcrypt->verify($password,$hash_password_db->password)) + { + return TRUE; + } + + return FALSE; + } + + // sha1 + if ($this->store_salt) + { + $db_password = sha1($password . $hash_password_db->salt); + } + else + { + $salt = substr($hash_password_db->password, 0, $this->salt_length); + + $db_password = $salt . substr(sha1($salt . $password), 0, -$this->salt_length); + } + + if($db_password == $hash_password_db->password) + { + return TRUE; + } + else + { + return FALSE; + } + } + + /** + * Generates a random salt value for forgotten passwords or any other keys. Uses SHA1. + * + * @return void + * @author Mathew + **/ + public function hash_code($password) + { + return $this->hash_password($password, FALSE, TRUE); + } + + /** + * Generates a random salt value. + * + * Salt generation code taken from https://github.com/ircmaxell/password_compat/blob/master/lib/password.php + * + * @return void + * @author Anthony Ferrera + **/ + public function salt() + { + + $raw_salt_len = 16; + + $buffer = ''; + $buffer_valid = false; + + if (function_exists('random_bytes')) { + $buffer = random_bytes($raw_salt_len); + if ($buffer) { + $buffer_valid = true; + } + } + + if (!$buffer_valid && function_exists('mcrypt_create_iv') && !defined('PHALANGER')) { + $buffer = mcrypt_create_iv($raw_salt_len, MCRYPT_DEV_URANDOM); + if ($buffer) { + $buffer_valid = true; + } + } + + if (!$buffer_valid && function_exists('openssl_random_pseudo_bytes')) { + $buffer = openssl_random_pseudo_bytes($raw_salt_len); + if ($buffer) { + $buffer_valid = true; + } + } + + if (!$buffer_valid && @is_readable('/dev/urandom')) { + $f = fopen('/dev/urandom', 'r'); + $read = strlen($buffer); + while ($read < $raw_salt_len) { + $buffer .= fread($f, $raw_salt_len - $read); + $read = strlen($buffer); + } + fclose($f); + if ($read >= $raw_salt_len) { + $buffer_valid = true; + } + } + + if (!$buffer_valid || strlen($buffer) < $raw_salt_len) { + $bl = strlen($buffer); + for ($i = 0; $i < $raw_salt_len; $i++) { + if ($i < $bl) { + $buffer[$i] = $buffer[$i] ^ chr(mt_rand(0, 255)); + } else { + $buffer .= chr(mt_rand(0, 255)); + } + } + } + + $salt = $buffer; + + // encode string with the Base64 variant used by crypt + $base64_digits = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + $bcrypt64_digits = './ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + $base64_string = base64_encode($salt); + $salt = strtr(rtrim($base64_string, '='), $base64_digits, $bcrypt64_digits); + + $salt = substr($salt, 0, $this->salt_length); + + + return $salt; + + } + + /** + * Activation functions + * + * Activate : Validates and removes activation code. + * Deactivate : Updates a users row with an activation code. + * + * @author Mathew + */ + + /** + * activate + * + * @return void + * @author Mathew + **/ + public function activate($id, $code = false) + { + $this->trigger_events('pre_activate'); + + if ($code !== FALSE) + { + $query = $this->db->select($this->identity_column) + ->where('activation_code', $code) + ->where('id', $id) + ->limit(1) + ->order_by('id', 'desc') + ->get($this->tables['users']); + + $result = $query->row(); + + if ($query->num_rows() !== 1) + { + $this->trigger_events(array('post_activate', 'post_activate_unsuccessful')); + $this->set_error('activate_unsuccessful'); + return FALSE; + } + + $data = array( + 'activation_code' => NULL, + 'active' => 1 + ); + + $this->trigger_events('extra_where'); + $this->db->update($this->tables['users'], $data, array('id' => $id)); + } + else + { + $data = array( + 'activation_code' => NULL, + 'active' => 1 + ); + + + $this->trigger_events('extra_where'); + $this->db->update($this->tables['users'], $data, array('id' => $id)); + } + + + $return = $this->db->affected_rows() == 1; + if ($return) + { + $this->trigger_events(array('post_activate', 'post_activate_successful')); + $this->set_message('activate_successful'); + } + else + { + $this->trigger_events(array('post_activate', 'post_activate_unsuccessful')); + $this->set_error('activate_unsuccessful'); + } + + + return $return; + } + + + /** + * Deactivate + * + * @return void + * @author Mathew + **/ + public function deactivate($id = NULL) + { + $this->trigger_events('deactivate'); + + if (!isset($id)) + { + $this->set_error('deactivate_unsuccessful'); + return FALSE; + } + elseif($this->ion_auth->logged_in() && $this->user()->row()->id == $id) + { + $this->set_error('deactivate_current_user_unsuccessful'); + return FALSE; + } + + $activation_code = sha1(md5(microtime())); + $this->activation_code = $activation_code; + + $data = array( + 'activation_code' => $activation_code, + 'active' => 0 + ); + + $this->trigger_events('extra_where'); + $this->db->update($this->tables['users'], $data, array('id' => $id)); + + $return = $this->db->affected_rows() == 1; + if ($return) + $this->set_message('deactivate_successful'); + else + $this->set_error('deactivate_unsuccessful'); + + return $return; + } + + public function clear_forgotten_password_code($code) { + + if (empty($code)) + { + return FALSE; + } + + $this->db->where('forgotten_password_code', $code); + + if ($this->db->count_all_results($this->tables['users']) > 0) + { + $data = array( + 'forgotten_password_code' => NULL, + 'forgotten_password_time' => NULL + ); + + $this->db->update($this->tables['users'], $data, array('forgotten_password_code' => $code)); + + return TRUE; + } + + return FALSE; + } + + /** + * reset password + * + * @return bool + * @author Mathew + **/ + public function reset_password($identity, $new) { + $this->trigger_events('pre_change_password'); + + if (!$this->identity_check($identity)) { + $this->trigger_events(array('post_change_password', 'post_change_password_unsuccessful')); + return FALSE; + } + + $this->trigger_events('extra_where'); + + $query = $this->db->select('id, password, salt') + ->where($this->identity_column, $identity) + ->limit(1) + ->order_by('id', 'desc') + ->get($this->tables['users']); + + if ($query->num_rows() !== 1) + { + $this->trigger_events(array('post_change_password', 'post_change_password_unsuccessful')); + $this->set_error('password_change_unsuccessful'); + return FALSE; + } + + $result = $query->row(); + + $new = $this->hash_password($new, $result->salt); + + // store the new password and reset the remember code so all remembered instances have to re-login + // also clear the forgotten password code + $data = array( + 'password' => $new, + 'remember_code' => NULL, + 'forgotten_password_code' => NULL, + 'forgotten_password_time' => NULL, + ); + + $this->trigger_events('extra_where'); + $this->db->update($this->tables['users'], $data, array($this->identity_column => $identity)); + + $return = $this->db->affected_rows() == 1; + if ($return) + { + $this->trigger_events(array('post_change_password', 'post_change_password_successful')); + $this->set_message('password_change_successful'); + } + else + { + $this->trigger_events(array('post_change_password', 'post_change_password_unsuccessful')); + $this->set_error('password_change_unsuccessful'); + } + + return $return; + } + + /** + * change password + * + * @return bool + * @author Mathew + **/ + public function change_password($identity, $old, $new) + { + $this->trigger_events('pre_change_password'); + + $this->trigger_events('extra_where'); + + $query = $this->db->select('id, password, salt') + ->where($this->identity_column, $identity) + ->limit(1) + ->order_by('id', 'desc') + ->get($this->tables['users']); + + if ($query->num_rows() !== 1) + { + $this->trigger_events(array('post_change_password', 'post_change_password_unsuccessful')); + $this->set_error('password_change_unsuccessful'); + return FALSE; + } + + $user = $query->row(); + + $old_password_matches = $this->hash_password_db($user->id, $old); + + if ($old_password_matches === TRUE) + { + // store the new password and reset the remember code so all remembered instances have to re-login + $hashed_new_password = $this->hash_password($new, $user->salt); + $data = array( + 'password' => $hashed_new_password, + 'remember_code' => NULL, + ); + + $this->trigger_events('extra_where'); + + $successfully_changed_password_in_db = $this->db->update($this->tables['users'], $data, array($this->identity_column => $identity)); + if ($successfully_changed_password_in_db) + { + $this->trigger_events(array('post_change_password', 'post_change_password_successful')); + $this->set_message('password_change_successful'); + } + else + { + $this->trigger_events(array('post_change_password', 'post_change_password_unsuccessful')); + $this->set_error('password_change_unsuccessful'); + } + + return $successfully_changed_password_in_db; + } + + $this->set_error('password_change_unsuccessful'); + return FALSE; + } + + /** + * Checks username + * + * @return bool + * @author Mathew + **/ + public function username_check($username = '') + { + $this->trigger_events('username_check'); + + if (empty($username)) + { + return FALSE; + } + + $this->trigger_events('extra_where'); + + return $this->db->where('username', $username) + ->group_by("id") + ->order_by("id", "ASC") + ->limit(1) + ->count_all_results($this->tables['users']) > 0; + } + + /** + * Checks email + * + * @return bool + * @author Mathew + **/ + public function email_check($email = '') + { + $this->trigger_events('email_check'); + + if (empty($email)) + { + return FALSE; + } + + $this->trigger_events('extra_where'); + + return $this->db->where('email', $email) + ->group_by("id") + ->order_by("id", "ASC") + ->limit(1) + ->count_all_results($this->tables['users']) > 0; + } + + /** + * Identity check + * + * @return bool + * @author Mathew + **/ + public function identity_check($identity = '') + { + $this->trigger_events('identity_check'); + + if (empty($identity)) + { + return FALSE; + } + + return $this->db->where($this->identity_column, $identity) + ->count_all_results($this->tables['users']) > 0; + } + + /** + * Insert a forgotten password key. + * + * @return bool + * @author Mathew + * @updated Ryan + * @updated 52aa456eef8b60ad6754b31fbdcc77bb + **/ + public function forgotten_password($identity) + { + if (empty($identity)) + { + $this->trigger_events(array('post_forgotten_password', 'post_forgotten_password_unsuccessful')); + return FALSE; + } + + // All some more randomness + $activation_code_part = ""; + if(function_exists("openssl_random_pseudo_bytes")) { + $activation_code_part = openssl_random_pseudo_bytes(128); + } + + for($i=0;$i<1024;$i++) { + $activation_code_part = sha1($activation_code_part . mt_rand() . microtime()); + } + + $key = $this->hash_code($activation_code_part.$identity); + + // If enable query strings is set, then we need to replace any unsafe characters so that the code can still work + if ($key != '' && $this->config->item('permitted_uri_chars') != '' && $this->config->item('enable_query_strings') == FALSE) + { + // preg_quote() in PHP 5.3 escapes -, so the str_replace() and addition of - to preg_quote() is to maintain backwards + // compatibility as many are unaware of how characters in the permitted_uri_chars will be parsed as a regex pattern + if ( ! preg_match("|^[".str_replace(array('\\-', '\-'), '-', preg_quote($this->config->item('permitted_uri_chars'), '-'))."]+$|i", $key)) + { + $key = preg_replace("/[^".$this->config->item('permitted_uri_chars')."]+/i", "-", $key); + } + } + + // Limit to 40 characters since that's how our DB field is setup + $this->forgotten_password_code = substr($key, 0, 40); + + $this->trigger_events('extra_where'); + + $update = array( + 'forgotten_password_code' => $key, + 'forgotten_password_time' => time() + ); + + $this->db->update($this->tables['users'], $update, array($this->identity_column => $identity)); + + $return = $this->db->affected_rows() == 1; + + if ($return) + $this->trigger_events(array('post_forgotten_password', 'post_forgotten_password_successful')); + else + $this->trigger_events(array('post_forgotten_password', 'post_forgotten_password_unsuccessful')); + + return $return; + } + + /** + * Forgotten Password Complete + * + * @return string + * @author Mathew + **/ + public function forgotten_password_complete($code, $salt=FALSE) + { + $this->trigger_events('pre_forgotten_password_complete'); + + if (empty($code)) + { + $this->trigger_events(array('post_forgotten_password_complete', 'post_forgotten_password_complete_unsuccessful')); + return FALSE; + } + + $profile = $this->where('forgotten_password_code', $code)->users()->row(); //pass the code to profile + + if ($profile) { + + if ($this->config->item('forgot_password_expiration', 'ion_auth') > 0) { + //Make sure it isn't expired + $expiration = $this->config->item('forgot_password_expiration', 'ion_auth'); + if (time() - $profile->forgotten_password_time > $expiration) { + //it has expired + $this->set_error('forgot_password_expired'); + $this->trigger_events(array('post_forgotten_password_complete', 'post_forgotten_password_complete_unsuccessful')); + return FALSE; + } + } + + $password = $this->salt(); + + $data = array( + 'password' => $this->hash_password($password, $salt), + 'forgotten_password_code' => NULL, + 'active' => 1, + ); + + $this->db->update($this->tables['users'], $data, array('forgotten_password_code' => $code)); + + $this->trigger_events(array('post_forgotten_password_complete', 'post_forgotten_password_complete_successful')); + return $password; + } + + $this->trigger_events(array('post_forgotten_password_complete', 'post_forgotten_password_complete_unsuccessful')); + return FALSE; + } + + /** + * register + * + * @return bool + * @author Mathew + **/ + public function register($identity, $password, $email, $additional_data = array(), $groups = array()) + { + $this->trigger_events('pre_register'); + + $manual_activation = $this->config->item('manual_activation', 'ion_auth'); + + if ($this->identity_check($identity)) + { + $this->set_error('account_creation_duplicate_identity'); + return FALSE; + } + elseif ( !$this->config->item('default_group', 'ion_auth') && empty($groups) ) + { + $this->set_error('account_creation_missing_default_group'); + return FALSE; + } + + // check if the default set in config exists in database + $query = $this->db->get_where($this->tables['groups'],array('name' => $this->config->item('default_group', 'ion_auth')),1)->row(); + if( !isset($query->id) && empty($groups) ) + { + $this->set_error('account_creation_invalid_default_group'); + return FALSE; + } + + // capture default group details + $default_group = $query; + + // IP Address + $ip_address = $this->_prepare_ip($this->input->ip_address()); + $salt = $this->store_salt ? $this->salt() : FALSE; + $password = $this->hash_password($password, $salt); + + // Users table. + $data = array( + $this->identity_column => $identity, + 'username' => $identity, + 'password' => $password, + 'email' => $email, + 'ip_address' => $ip_address, + 'created_on' => time(), + 'active' => ($manual_activation === false ? 1 : 0) + ); + + if ($this->store_salt) + { + $data['salt'] = $salt; + } + + // filter out any data passed that doesnt have a matching column in the users table + // and merge the set user data and the additional data + $user_data = array_merge($this->_filter_data($this->tables['users'], $additional_data), $data); + + $this->trigger_events('extra_set'); + + $this->db->insert($this->tables['users'], $user_data); + + $id = $this->db->insert_id($this->tables['users'] . '_id_seq'); + + // add in groups array if it doesn't exists and stop adding into default group if default group ids are set + if( isset($default_group->id) && empty($groups) ) + { + $groups[] = $default_group->id; + } + + if (!empty($groups)) + { + // add to groups + foreach ($groups as $group) + { + $this->add_to_group($group, $id); + } + } + + $this->trigger_events('post_register'); + + return (isset($id)) ? $id : FALSE; + } + + /** + * login + * + * @return bool + * @author Mathew + **/ + public function login($identity, $password, $remember=FALSE) + { + $this->trigger_events('pre_login'); + + if (empty($identity) || empty($password)) + { + $this->set_error('login_unsuccessful'); + return FALSE; + } + + $this->trigger_events('extra_where'); + + $query = $this->db->select($this->identity_column . ', email, id, password, active, last_login') + ->where($this->identity_column, $identity) + ->limit(1) + ->order_by('id', 'desc') + ->get($this->tables['users']); + + if($this->is_max_login_attempts_exceeded($identity)) + { + // Hash something anyway, just to take up time + $this->hash_password($password); + + $this->trigger_events('post_login_unsuccessful'); + $this->set_error('login_timeout'); + + return FALSE; + } + + if ($query->num_rows() === 1) + { + $user = $query->row(); + + $password = $this->hash_password_db($user->id, $password); + + if ($password === TRUE) + { + if ($user->active == 0) + { + $this->trigger_events('post_login_unsuccessful'); + $this->set_error('login_unsuccessful_not_active'); + + return FALSE; + } + + $this->set_session($user); + + $this->update_last_login($user->id); + + $this->clear_login_attempts($identity); + + if ($remember && $this->config->item('remember_users', 'ion_auth')) + { + $this->remember_user($user->id); + } + + $this->trigger_events(array('post_login', 'post_login_successful')); + $this->set_message('login_successful'); + + return TRUE; + } + } + + // Hash something anyway, just to take up time + $this->hash_password($password); + + $this->increase_login_attempts($identity); + + $this->trigger_events('post_login_unsuccessful'); + $this->set_error('login_unsuccessful'); + + return FALSE; + } + + /** + * recheck_session verifies if the session should be rechecked according to + * the configuration item recheck_timer. If it does, then it will check if the user is still active + * @return bool + */ + public function recheck_session() + { + $recheck = (null !== $this->config->item('recheck_timer', 'ion_auth')) ? $this->config->item('recheck_timer', 'ion_auth') : 0; + + if($recheck!==0) + { + $last_login = $this->session->userdata('last_check'); + if($last_login+$recheck < time()) + { + $query = $this->db->select('id') + ->where(array($this->identity_column=>$this->session->userdata('identity'),'active'=>'1')) + ->limit(1) + ->order_by('id', 'desc') + ->get($this->tables['users']); + if ($query->num_rows() === 1) + { + $this->session->set_userdata('last_check',time()); + } + else + { + $this->trigger_events('logout'); + + $identity = $this->config->item('identity', 'ion_auth'); + + if (substr(CI_VERSION, 0, 1) == '2') + { + $this->session->unset_userdata( array($identity => '', 'id' => '', 'user_id' => '') ); + } + else + { + $this->session->unset_userdata( array($identity, 'id', 'user_id') ); + } + return false; + } + } + } + + return (bool) $this->session->userdata('identity'); + } + + /** + * is_max_login_attempts_exceeded + * Based on code from Tank Auth, by Ilya Konyukhov (https://github.com/ilkon/Tank-Auth) + * + * @param string $identity: user's identity + * @param string $ip_address: IP address + * Only used if track_login_ip_address set to TRUE. + * If NULL (default value), current IP address is used. + * Use get_last_attempt_ip($identity) to retrieve user's last IP + * @return boolean + **/ + public function is_max_login_attempts_exceeded($identity, $ip_address = NULL) { + if ($this->config->item('track_login_attempts', 'ion_auth')) { + $max_attempts = $this->config->item('maximum_login_attempts', 'ion_auth'); + if ($max_attempts > 0) { + $attempts = $this->get_attempts_num($identity, $ip_address); + return $attempts >= $max_attempts; + } + } + return FALSE; + } + + /** + * Get number of attempts to login occured from given IP-address or identity + * Based on code from Tank Auth, by Ilya Konyukhov (https://github.com/ilkon/Tank-Auth) + * + * @param string $identity: user's identity + * @param string $ip_address: IP address + * Only used if track_login_ip_address set to TRUE. + * If NULL (default value), current IP address is used. + * Use get_last_attempt_ip($identity) to retrieve user's last IP + * @return int + */ + public function get_attempts_num($identity, $ip_address = NULL) + { + if ($this->config->item('track_login_attempts', 'ion_auth')) { + $this->db->select('1', FALSE); + $this->db->where('login', $identity); + if ($this->config->item('track_login_ip_address', 'ion_auth')) { + if (!isset($ip_address)) { + $ip_address = $this->_prepare_ip($this->input->ip_address()); + } + $this->db->where('ip_address', $ip_address); + } + $this->db->where('time >', time() - $this->config->item('lockout_time', 'ion_auth'), FALSE); + $qres = $this->db->get($this->tables['login_attempts']); + return $qres->num_rows(); + } + return 0; + } + + /** + * Get a boolean to determine if an account should be locked out due to + * exceeded login attempts within a given period + * + * This function is only a wrapper for is_max_login_attempts_exceeded() since it + * only retrieve attempts within the given period. + * It is kept for retrocompatibility purpose. + * + * @param string $identity: user's identity + * @param string $ip_address: IP address + * Only used if track_login_ip_address set to TRUE. + * If NULL (default value), current IP address is used. + * Use get_last_attempt_ip($identity) to retrieve user's last IP + * @return boolean + */ + public function is_time_locked_out($identity, $ip_address = NULL) { + return $this->is_max_login_attempts_exceeded($identity, $ip_address); + } + + /** + * Get the time of the last time a login attempt occured from given IP-address or identity + * + * This function is no longer used. + * It is kept for retrocompatibility purpose. + * + * @param string $identity: user's identity + * @param string $ip_address: IP address + * Only used if track_login_ip_address set to TRUE. + * If NULL (default value), current IP address is used. + * Use get_last_attempt_ip($identity) to retrieve user's last IP + * @return int + */ + public function get_last_attempt_time($identity, $ip_address = NULL) { + if ($this->config->item('track_login_attempts', 'ion_auth')) { + $this->db->select('time'); + $this->db->where('login', $identity); + if ($this->config->item('track_login_ip_address', 'ion_auth')) { + if (!isset($ip_address)) { + $ip_address = $this->_prepare_ip($this->input->ip_address()); + } + $this->db->where('ip_address', $ip_address); + } + $this->db->order_by('id', 'desc'); + $qres = $this->db->get($this->tables['login_attempts'], 1); + + if($qres->num_rows() > 0) { + return $qres->row()->time; + } + } + + return 0; + } + + /** + * Get the IP address of the last time a login attempt occured from given identity + * + * @param string $identity: user's identity + * @return string + */ + public function get_last_attempt_ip($identity) { + if ($this->config->item('track_login_attempts', 'ion_auth') && $this->config->item('track_login_ip_address', 'ion_auth')) { + $this->db->select('ip_address'); + $this->db->where('login', $identity); + $this->db->order_by('id', 'desc'); + $qres = $this->db->get($this->tables['login_attempts'], 1); + + if($qres->num_rows() > 0) { + return $qres->row()->ip_address; + } + } + + return ''; + } + + /** + * increase_login_attempts + * Based on code from Tank Auth, by Ilya Konyukhov (https://github.com/ilkon/Tank-Auth) + * + * Note: the current IP address will be used if track_login_ip_address config value is TRUE + * + * @param string $identity: user's identity + **/ + public function increase_login_attempts($identity) { + if ($this->config->item('track_login_attempts', 'ion_auth')) { + $data = array('ip_address' => '', 'login' => $identity, 'time' => time()); + if ($this->config->item('track_login_ip_address', 'ion_auth')) { + $data['ip_address'] = $this->_prepare_ip($this->input->ip_address()); + } + return $this->db->insert($this->tables['login_attempts'], $data); + } + return FALSE; + } + + /** + * clear_login_attempts + * Based on code from Tank Auth, by Ilya Konyukhov (https://github.com/ilkon/Tank-Auth) + * + * @param string $identity: user's identity + * @param int $old_attempts_expire_period: in seconds, any attempts older than this value will be removed. + * It is used for regularly purging the attempts table. + * (for security reason, minimum value is lockout_time config value) + * @param string $ip_address: IP address + * Only used if track_login_ip_address set to TRUE. + * If NULL (default value), current IP address is used. + * Use get_last_attempt_ip($identity) to retrieve user's last IP + **/ + public function clear_login_attempts($identity, $old_attempts_expire_period = 86400, $ip_address = NULL) { + if ($this->config->item('track_login_attempts', 'ion_auth')) { + // Make sure $old_attempts_expire_period is at least equals to lockout_time + $old_attempts_expire_period = max($old_attempts_expire_period, $this->config->item('lockout_time', 'ion_auth')); + + $this->db->where('login', $identity); + if ($this->config->item('track_login_ip_address', 'ion_auth')) { + if (!isset($ip_address)) { + $ip_address = $this->_prepare_ip($this->input->ip_address()); + } + $this->db->where('ip_address', $ip_address); + } + // Purge obsolete login attempts + $this->db->or_where('time <', time() - $old_attempts_expire_period, FALSE); + + return $this->db->delete($this->tables['login_attempts']); + } + return FALSE; + } + + public function limit($limit) + { + $this->trigger_events('limit'); + $this->_ion_limit = $limit; + + return $this; + } + + public function offset($offset) + { + $this->trigger_events('offset'); + $this->_ion_offset = $offset; + + return $this; + } + + public function where($where, $value = NULL) + { + $this->trigger_events('where'); + + if (!is_array($where)) + { + $where = array($where => $value); + } + + array_push($this->_ion_where, $where); + + return $this; + } + + public function like($like, $value = NULL, $position = 'both') + { + $this->trigger_events('like'); + + array_push($this->_ion_like, array( + 'like' => $like, + 'value' => $value, + 'position' => $position + )); + + return $this; + } + + public function select($select) + { + $this->trigger_events('select'); + + $this->_ion_select[] = $select; + + return $this; + } + + public function order_by($by, $order='desc') + { + $this->trigger_events('order_by'); + + $this->_ion_order_by = $by; + $this->_ion_order = $order; + + return $this; + } + + public function row() + { + $this->trigger_events('row'); + + $row = $this->response->row(); + + return $row; + } + + public function row_array() + { + $this->trigger_events(array('row', 'row_array')); + + $row = $this->response->row_array(); + + return $row; + } + + public function result() + { + $this->trigger_events('result'); + + $result = $this->response->result(); + + return $result; + } + + public function result_array() + { + $this->trigger_events(array('result', 'result_array')); + + $result = $this->response->result_array(); + + return $result; + } + + public function num_rows() + { + $this->trigger_events(array('num_rows')); + + $result = $this->response->num_rows(); + + return $result; + } + + /** + * users + * + * @return object Users + * @author Ben Edmunds + **/ + public function users($groups = NULL) + { + $this->trigger_events('users'); + + if (isset($this->_ion_select) && !empty($this->_ion_select)) + { + foreach ($this->_ion_select as $select) + { + $this->db->select($select); + } + + $this->_ion_select = array(); + } + else + { + //default selects + $this->db->select(array( + $this->tables['users'].'.*', + $this->tables['users'].'.id as id', + $this->tables['users'].'.id as user_id' + )); + } + + // filter by group id(s) if passed + if (isset($groups)) + { + // build an array if only one group was passed + if (!is_array($groups)) + { + $groups = Array($groups); + } + + // join and then run a where_in against the group ids + if (isset($groups) && !empty($groups)) + { + $this->db->distinct(); + $this->db->join( + $this->tables['users_groups'], + $this->tables['users_groups'].'.'.$this->join['users'].'='.$this->tables['users'].'.id', + 'inner' + ); + } + + // verify if group name or group id was used and create and put elements in different arrays + $group_ids = array(); + $group_names = array(); + foreach($groups as $group) + { + if(is_numeric($group)) $group_ids[] = $group; + else $group_names[] = $group; + } + $or_where_in = (!empty($group_ids) && !empty($group_names)) ? 'or_where_in' : 'where_in'; + // if group name was used we do one more join with groups + if(!empty($group_names)) + { + $this->db->join($this->tables['groups'], $this->tables['users_groups'] . '.' . $this->join['groups'] . ' = ' . $this->tables['groups'] . '.id', 'inner'); + $this->db->where_in($this->tables['groups'] . '.name', $group_names); + } + if(!empty($group_ids)) + { + $this->db->{$or_where_in}($this->tables['users_groups'].'.'.$this->join['groups'], $group_ids); + } + } + + $this->trigger_events('extra_where'); + + // run each where that was passed + if (isset($this->_ion_where) && !empty($this->_ion_where)) + { + foreach ($this->_ion_where as $where) + { + $this->db->where($where); + } + + $this->_ion_where = array(); + } + + if (isset($this->_ion_like) && !empty($this->_ion_like)) + { + foreach ($this->_ion_like as $like) + { + $this->db->or_like($like['like'], $like['value'], $like['position']); + } + + $this->_ion_like = array(); + } + + if (isset($this->_ion_limit) && isset($this->_ion_offset)) + { + $this->db->limit($this->_ion_limit, $this->_ion_offset); + + $this->_ion_limit = NULL; + $this->_ion_offset = NULL; + } + else if (isset($this->_ion_limit)) + { + $this->db->limit($this->_ion_limit); + + $this->_ion_limit = NULL; + } + + // set the order + if (isset($this->_ion_order_by) && isset($this->_ion_order)) + { + $this->db->order_by($this->_ion_order_by, $this->_ion_order); + + $this->_ion_order = NULL; + $this->_ion_order_by = NULL; + } + + $this->response = $this->db->get($this->tables['users']); + + return $this; + } + + /** + * user + * + * @return object + * @author Ben Edmunds + **/ + public function user($id = NULL) + { + $this->trigger_events('user'); + + // if no id was passed use the current users id + $id = isset($id) ? $id : $this->session->userdata('user_id'); + + $this->limit(1); + $this->order_by($this->tables['users'].'.id', 'desc'); + $this->where($this->tables['users'].'.id', $id); + + $this->users(); + + return $this; + } + + /** + * get_users_groups + * + * @return array + * @author Ben Edmunds + **/ + public function get_users_groups($id=FALSE) + { + $this->trigger_events('get_users_group'); + + // if no id was passed use the current users id + $id || $id = $this->session->userdata('user_id'); + + return $this->db->select($this->tables['users_groups'].'.'.$this->join['groups'].' as id, '.$this->tables['groups'].'.name, '.$this->tables['groups'].'.description') + ->where($this->tables['users_groups'].'.'.$this->join['users'], $id) + ->join($this->tables['groups'], $this->tables['users_groups'].'.'.$this->join['groups'].'='.$this->tables['groups'].'.id') + ->get($this->tables['users_groups']); + } + + /** + * add_to_group + * + * @return bool + * @author Ben Edmunds + **/ + public function add_to_group($group_ids, $user_id=false) + { + $this->trigger_events('add_to_group'); + + // if no id was passed use the current users id + $user_id || $user_id = $this->session->userdata('user_id'); + + if(!is_array($group_ids)) + { + $group_ids = array($group_ids); + } + + $return = 0; + + // Then insert each into the database + foreach ($group_ids as $group_id) + { + if ($this->db->insert($this->tables['users_groups'], array( $this->join['groups'] => (float)$group_id, $this->join['users'] => (float)$user_id))) + { + if (isset($this->_cache_groups[$group_id])) { + $group_name = $this->_cache_groups[$group_id]; + } + else { + $group = $this->group($group_id)->result(); + $group_name = $group[0]->name; + $this->_cache_groups[$group_id] = $group_name; + } + $this->_cache_user_in_group[$user_id][$group_id] = $group_name; + + // Return the number of groups added + $return += 1; + } + } + + return $return; + } + + /** + * remove_from_group + * + * @return bool + * @author Ben Edmunds + **/ + public function remove_from_group($group_ids=false, $user_id=false) + { + $this->trigger_events('remove_from_group'); + + // user id is required + if(empty($user_id)) + { + return FALSE; + } + + // if group id(s) are passed remove user from the group(s) + if( ! empty($group_ids)) + { + if(!is_array($group_ids)) + { + $group_ids = array($group_ids); + } + + foreach($group_ids as $group_id) + { + $this->db->delete($this->tables['users_groups'], array($this->join['groups'] => (float)$group_id, $this->join['users'] => (float)$user_id)); + if (isset($this->_cache_user_in_group[$user_id]) && isset($this->_cache_user_in_group[$user_id][$group_id])) + { + unset($this->_cache_user_in_group[$user_id][$group_id]); + } + } + + $return = TRUE; + } + // otherwise remove user from all groups + else + { + if ($return = $this->db->delete($this->tables['users_groups'], array($this->join['users'] => (float)$user_id))) { + $this->_cache_user_in_group[$user_id] = array(); + } + } + return $return; + } + + /** + * groups + * + * @return object + * @author Ben Edmunds + **/ + public function groups() + { + $this->trigger_events('groups'); + + // run each where that was passed + if (isset($this->_ion_where) && !empty($this->_ion_where)) + { + foreach ($this->_ion_where as $where) + { + $this->db->where($where); + } + $this->_ion_where = array(); + } + + if (isset($this->_ion_limit) && isset($this->_ion_offset)) + { + $this->db->limit($this->_ion_limit, $this->_ion_offset); + + $this->_ion_limit = NULL; + $this->_ion_offset = NULL; + } + else if (isset($this->_ion_limit)) + { + $this->db->limit($this->_ion_limit); + + $this->_ion_limit = NULL; + } + + // set the order + if (isset($this->_ion_order_by) && isset($this->_ion_order)) + { + $this->db->order_by($this->_ion_order_by, $this->_ion_order); + } + + $this->response = $this->db->get($this->tables['groups']); + + return $this; + } + + /** + * group + * + * @return object + * @author Ben Edmunds + **/ + public function group($id = NULL) + { + $this->trigger_events('group'); + + if (isset($id)) + { + $this->where($this->tables['groups'].'.id', $id); + } + + $this->limit(1); + $this->order_by('id', 'desc'); + + return $this->groups(); + } + + /** + * update + * + * @return bool + * @author Phil Sturgeon + **/ + public function update($id, array $data) + { + $this->trigger_events('pre_update_user'); + + $user = $this->user($id)->row(); + + $this->db->trans_begin(); + + if (array_key_exists($this->identity_column, $data) && $this->identity_check($data[$this->identity_column]) && $user->{$this->identity_column} !== $data[$this->identity_column]) + { + $this->db->trans_rollback(); + $this->set_error('account_creation_duplicate_identity'); + + $this->trigger_events(array('post_update_user', 'post_update_user_unsuccessful')); + $this->set_error('update_unsuccessful'); + + return FALSE; + } + + // Filter the data passed + $data = $this->_filter_data($this->tables['users'], $data); + + if (array_key_exists($this->identity_column, $data) || array_key_exists('password', $data) || array_key_exists('email', $data)) + { + if (array_key_exists('password', $data)) + { + if( ! empty($data['password'])) + { + $data['password'] = $this->hash_password($data['password'], $user->salt); + } + else + { + // unset password so it doesn't effect database entry if no password passed + unset($data['password']); + } + } + } + + $this->trigger_events('extra_where'); + $this->db->update($this->tables['users'], $data, array('id' => $user->id)); + + if ($this->db->trans_status() === FALSE) + { + $this->db->trans_rollback(); + + $this->trigger_events(array('post_update_user', 'post_update_user_unsuccessful')); + $this->set_error('update_unsuccessful'); + return FALSE; + } + + $this->db->trans_commit(); + + $this->trigger_events(array('post_update_user', 'post_update_user_successful')); + $this->set_message('update_successful'); + return TRUE; + } + + /** + * delete_user + * + * @return bool + * @author Phil Sturgeon + **/ + public function delete_user($id) + { + $this->trigger_events('pre_delete_user'); + + $this->db->trans_begin(); + + // remove user from groups + $this->remove_from_group(NULL, $id); + + // delete user from users table should be placed after remove from group + $this->db->delete($this->tables['users'], array('id' => $id)); + + + if ($this->db->trans_status() === FALSE) + { + $this->db->trans_rollback(); + $this->trigger_events(array('post_delete_user', 'post_delete_user_unsuccessful')); + $this->set_error('delete_unsuccessful'); + return FALSE; + } + + $this->db->trans_commit(); + + $this->trigger_events(array('post_delete_user', 'post_delete_user_successful')); + $this->set_message('delete_successful'); + return TRUE; + } + + /** + * update_last_login + * + * @return bool + * @author Ben Edmunds + **/ + public function update_last_login($id) + { + $this->trigger_events('update_last_login'); + + $this->load->helper('date'); + + $this->trigger_events('extra_where'); + + $this->db->update($this->tables['users'], array('last_login' => time()), array('id' => $id)); + + return $this->db->affected_rows() == 1; + } + + /** + * set_lang + * + * @return bool + * @author Ben Edmunds + **/ + public function set_lang($lang = 'en') + { + $this->trigger_events('set_lang'); + + // if the user_expire is set to zero we'll set the expiration two years from now. + if($this->config->item('user_expire', 'ion_auth') === 0) + { + $expire = (60*60*24*365*2); + } + // otherwise use what is set + else + { + $expire = $this->config->item('user_expire', 'ion_auth'); + } + + set_cookie(array( + 'name' => 'lang_code', + 'value' => $lang, + 'expire' => $expire + )); + + return TRUE; + } + + /** + * set_session + * + * @return bool + * @author jrmadsen67 + **/ + public function set_session($user) + { + + $this->trigger_events('pre_set_session'); + + $session_data = array( + 'identity' => $user->{$this->identity_column}, + $this->identity_column => $user->{$this->identity_column}, + 'email' => $user->email, + 'user_id' => $user->id, //everyone likes to overwrite id so we'll use user_id + 'old_last_login' => $user->last_login, + 'last_check' => time(), + ); + + $this->session->set_userdata($session_data); + + $this->trigger_events('post_set_session'); + + return TRUE; + } + + /** + * remember_user + * + * @return bool + * @author Ben Edmunds + **/ + public function remember_user($id) + { + $this->trigger_events('pre_remember_user'); + + if (!$id) + { + return FALSE; + } + + $user = $this->user($id)->row(); + + $salt = $this->salt(); + + $this->db->update($this->tables['users'], array('remember_code' => $salt), array('id' => $id)); + + if ($this->db->affected_rows() > -1) + { + // if the user_expire is set to zero we'll set the expiration two years from now. + if($this->config->item('user_expire', 'ion_auth') === 0) + { + $expire = (60*60*24*365*2); + } + // otherwise use what is set + else + { + $expire = $this->config->item('user_expire', 'ion_auth'); + } + + set_cookie(array( + 'name' => $this->config->item('identity_cookie_name', 'ion_auth'), + 'value' => $user->{$this->identity_column}, + 'expire' => $expire + )); + + set_cookie(array( + 'name' => $this->config->item('remember_cookie_name', 'ion_auth'), + 'value' => $salt, + 'expire' => $expire + )); + + $this->trigger_events(array('post_remember_user', 'remember_user_successful')); + return TRUE; + } + + $this->trigger_events(array('post_remember_user', 'remember_user_unsuccessful')); + return FALSE; + } + + /** + * login_remembed_user + * + * @return bool + * @author Ben Edmunds + **/ + public function login_remembered_user() + { + $this->trigger_events('pre_login_remembered_user'); + + // check for valid data + if (!get_cookie($this->config->item('identity_cookie_name', 'ion_auth')) + || !get_cookie($this->config->item('remember_cookie_name', 'ion_auth')) + || !$this->identity_check(get_cookie($this->config->item('identity_cookie_name', 'ion_auth')))) + { + $this->trigger_events(array('post_login_remembered_user', 'post_login_remembered_user_unsuccessful')); + return FALSE; + } + + // get the user + $this->trigger_events('extra_where'); + $query = $this->db->select($this->identity_column.', id, email, last_login') + ->where($this->identity_column, urldecode(get_cookie($this->config->item('identity_cookie_name', 'ion_auth')))) + ->where('remember_code', get_cookie($this->config->item('remember_cookie_name', 'ion_auth'))) + ->where('active',1) + ->limit(1) + ->order_by('id', 'desc') + ->get($this->tables['users']); + + // if the user was found, sign them in + if ($query->num_rows() == 1) + { + $user = $query->row(); + + $this->update_last_login($user->id); + + $this->set_session($user); + + // extend the users cookies if the option is enabled + if ($this->config->item('user_extend_on_login', 'ion_auth')) + { + $this->remember_user($user->id); + } + + $this->trigger_events(array('post_login_remembered_user', 'post_login_remembered_user_successful')); + return TRUE; + } + + $this->trigger_events(array('post_login_remembered_user', 'post_login_remembered_user_unsuccessful')); + return FALSE; + } + + + /** + * create_group + * + * @author aditya menon + */ + public function create_group($group_name = FALSE, $group_description = '', $additional_data = array()) + { + // bail if the group name was not passed + if(!$group_name) + { + $this->set_error('group_name_required'); + return FALSE; + } + + // bail if the group name already exists + $existing_group = $this->db->get_where($this->tables['groups'], array('name' => $group_name))->num_rows(); + if($existing_group !== 0) + { + $this->set_error('group_already_exists'); + return FALSE; + } + + $data = array('name'=>$group_name,'description'=>$group_description); + + // filter out any data passed that doesnt have a matching column in the groups table + // and merge the set group data and the additional data + if (!empty($additional_data)) $data = array_merge($this->_filter_data($this->tables['groups'], $additional_data), $data); + + $this->trigger_events('extra_group_set'); + + // insert the new group + $this->db->insert($this->tables['groups'], $data); + $group_id = $this->db->insert_id($this->tables['groups'] . '_id_seq'); + + // report success + $this->set_message('group_creation_successful'); + // return the brand new group id + return $group_id; + } + + /** + * update_group + * + * @return bool + * @author aditya menon + **/ + public function update_group($group_id = FALSE, $group_name = FALSE, $additional_data = array()) + { + if (empty($group_id)) return FALSE; + + $data = array(); + + if (!empty($group_name)) + { + // we are changing the name, so do some checks + + // bail if the group name already exists + $existing_group = $this->db->get_where($this->tables['groups'], array('name' => $group_name))->row(); + if(isset($existing_group->id) && $existing_group->id != $group_id) + { + $this->set_error('group_already_exists'); + return FALSE; + } + + $data['name'] = $group_name; + } + + // restrict change of name of the admin group + $group = $this->db->get_where($this->tables['groups'], array('id' => $group_id))->row(); + if($this->config->item('admin_group', 'ion_auth') === $group->name && $group_name !== $group->name) + { + $this->set_error('group_name_admin_not_alter'); + return FALSE; + } + + + // IMPORTANT!! Third parameter was string type $description; this following code is to maintain backward compatibility + // New projects should work with 3rd param as array + if (is_string($additional_data)) $additional_data = array('description' => $additional_data); + + + // filter out any data passed that doesnt have a matching column in the groups table + // and merge the set group data and the additional data + if (!empty($additional_data)) $data = array_merge($this->_filter_data($this->tables['groups'], $additional_data), $data); + + + $this->db->update($this->tables['groups'], $data, array('id' => $group_id)); + + $this->set_message('group_update_successful'); + + return TRUE; + } + + /** + * delete_group + * + * @return bool + * @author aditya menon + **/ + public function delete_group($group_id = FALSE) + { + // bail if mandatory param not set + if(!$group_id || empty($group_id)) + { + return FALSE; + } + $group = $this->group($group_id)->row(); + if($group->name == $this->config->item('admin_group', 'ion_auth')) + { + $this->trigger_events(array('post_delete_group', 'post_delete_group_notallowed')); + $this->set_error('group_delete_notallowed'); + return FALSE; + } + + $this->trigger_events('pre_delete_group'); + + $this->db->trans_begin(); + + // remove all users from this group + $this->db->delete($this->tables['users_groups'], array($this->join['groups'] => $group_id)); + // remove the group itself + $this->db->delete($this->tables['groups'], array('id' => $group_id)); + + if ($this->db->trans_status() === FALSE) + { + $this->db->trans_rollback(); + $this->trigger_events(array('post_delete_group', 'post_delete_group_unsuccessful')); + $this->set_error('group_delete_unsuccessful'); + return FALSE; + } + + $this->db->trans_commit(); + + $this->trigger_events(array('post_delete_group', 'post_delete_group_successful')); + $this->set_message('group_delete_successful'); + return TRUE; + } + + public function set_hook($event, $name, $class, $method, $arguments) + { + $this->_ion_hooks->{$event}[$name] = new stdClass; + $this->_ion_hooks->{$event}[$name]->class = $class; + $this->_ion_hooks->{$event}[$name]->method = $method; + $this->_ion_hooks->{$event}[$name]->arguments = $arguments; + } + + public function remove_hook($event, $name) + { + if (isset($this->_ion_hooks->{$event}[$name])) + { + unset($this->_ion_hooks->{$event}[$name]); + } + } + + public function remove_hooks($event) + { + if (isset($this->_ion_hooks->$event)) + { + unset($this->_ion_hooks->$event); + } + } + + protected function _call_hook($event, $name) + { + if (isset($this->_ion_hooks->{$event}[$name]) && method_exists($this->_ion_hooks->{$event}[$name]->class, $this->_ion_hooks->{$event}[$name]->method)) + { + $hook = $this->_ion_hooks->{$event}[$name]; + + return call_user_func_array(array($hook->class, $hook->method), $hook->arguments); + } + + return FALSE; + } + + public function trigger_events($events) + { + if (is_array($events) && !empty($events)) + { + foreach ($events as $event) + { + $this->trigger_events($event); + } + } + else + { + if (isset($this->_ion_hooks->$events) && !empty($this->_ion_hooks->$events)) + { + foreach ($this->_ion_hooks->$events as $name => $hook) + { + $this->_call_hook($events, $name); + } + } + } + } + + /** + * set_message_delimiters + * + * Set the message delimiters + * + * @return void + * @author Ben Edmunds + **/ + public function set_message_delimiters($start_delimiter, $end_delimiter) + { + $this->message_start_delimiter = $start_delimiter; + $this->message_end_delimiter = $end_delimiter; + + return TRUE; + } + + /** + * set_error_delimiters + * + * Set the error delimiters + * + * @return void + * @author Ben Edmunds + **/ + public function set_error_delimiters($start_delimiter, $end_delimiter) + { + $this->error_start_delimiter = $start_delimiter; + $this->error_end_delimiter = $end_delimiter; + + return TRUE; + } + + /** + * set_message + * + * Set a message + * + * @return void + * @author Ben Edmunds + **/ + public function set_message($message) + { + $this->messages[] = $message; + + return $message; + } + + + + /** + * messages + * + * Get the messages + * + * @return void + * @author Ben Edmunds + **/ + public function messages() + { + $_output = ''; + foreach ($this->messages as $message) + { + $messageLang = $this->lang->line($message) ? $this->lang->line($message) : '##' . $message . '##'; + $_output .= $this->message_start_delimiter . $messageLang . $this->message_end_delimiter; + } + + return $_output; + } + + /** + * messages as array + * + * Get the messages as an array + * + * @return array + * @author Raul Baldner Junior + **/ + public function messages_array($langify = TRUE) + { + if ($langify) + { + $_output = array(); + foreach ($this->messages as $message) + { + $messageLang = $this->lang->line($message) ? $this->lang->line($message) : '##' . $message . '##'; + $_output[] = $this->message_start_delimiter . $messageLang . $this->message_end_delimiter; + } + return $_output; + } + else + { + return $this->messages; + } + } + + + /** + * clear_messages + * + * Clear messages + * + * @return void + * @author Ben Edmunds + **/ + public function clear_messages() + { + $this->messages = array(); + + return TRUE; + } + + + /** + * set_error + * + * Set an error message + * + * @return void + * @author Ben Edmunds + **/ + public function set_error($error) + { + $this->errors[] = $error; + + return $error; + } + + /** + * errors + * + * Get the error message + * + * @return void + * @author Ben Edmunds + **/ + public function errors() + { + $_output = ''; + foreach ($this->errors as $error) + { + $errorLang = $this->lang->line($error) ? $this->lang->line($error) : '##' . $error . '##'; + $_output .= $this->error_start_delimiter . $errorLang . $this->error_end_delimiter; + } + + return $_output; + } + + /** + * errors as array + * + * Get the error messages as an array + * + * @return array + * @author Raul Baldner Junior + **/ + public function errors_array($langify = TRUE) + { + if ($langify) + { + $_output = array(); + foreach ($this->errors as $error) + { + $errorLang = $this->lang->line($error) ? $this->lang->line($error) : '##' . $error . '##'; + $_output[] = $this->error_start_delimiter . $errorLang . $this->error_end_delimiter; + } + return $_output; + } + else + { + return $this->errors; + } + } + + + /** + * clear_errors + * + * Clear Errors + * + * @return void + * @author Ben Edmunds + **/ + public function clear_errors() + { + $this->errors = array(); + + return TRUE; + } + + + + protected function _filter_data($table, $data) + { + $filtered_data = array(); + $columns = $this->db->list_fields($table); + + if (is_array($data)) + { + foreach ($columns as $column) + { + if (array_key_exists($column, $data)) + $filtered_data[$column] = $data[$column]; + } + } + + return $filtered_data; + } + + protected function _prepare_ip($ip_address) { + // just return the string IP address now for better compatibility + return $ip_address; + } +} diff --git a/application/modules/auth/views/change_password.php b/application/modules/auth/views/change_password.php new file mode 100644 index 0000000..b6f2a8c --- /dev/null +++ b/application/modules/auth/views/change_password.php @@ -0,0 +1,27 @@ + + +

+ +
+ + + +

+
+ +

+ +

+
+ +

+ +

+
+ +

+ + +

+ + diff --git a/application/modules/auth/views/create_group.php b/application/modules/auth/views/create_group.php new file mode 100644 index 0000000..69725e7 --- /dev/null +++ b/application/modules/auth/views/create_group.php @@ -0,0 +1,22 @@ + + +

+

+ +
+ + + +

+
+ +

+ +

+
+ +

+ +

+ + \ No newline at end of file diff --git a/application/modules/auth/views/create_user.php b/application/modules/auth/views/create_user.php new file mode 100644 index 0000000..b292d7b --- /dev/null +++ b/application/modules/auth/views/create_user.php @@ -0,0 +1,57 @@ +

+

+ +
+ + + +

+
+ +

+ +

+
+ +

+ + '; + echo lang('create_user_identity_label', 'identity'); + echo '
'; + echo form_error('identity'); + echo form_input($identity); + echo '

'; + } + ?> + +

+
+ +

+ +

+
+ +

+ +

+
+ +

+ +

+
+ +

+ +

+
+ +

+ + +

+ + diff --git a/application/modules/auth/views/deactivate_user.php b/application/modules/auth/views/deactivate_user.php new file mode 100644 index 0000000..9b1e5c5 --- /dev/null +++ b/application/modules/auth/views/deactivate_user.php @@ -0,0 +1,22 @@ + + + + +

+

username);?>

+ +id);?> + +

+ + + + +

+ + + $user->id)); ?> + +

+ + \ No newline at end of file diff --git a/application/modules/auth/views/edit_group.php b/application/modules/auth/views/edit_group.php new file mode 100644 index 0000000..dabbbd5 --- /dev/null +++ b/application/modules/auth/views/edit_group.php @@ -0,0 +1,21 @@ + +

+

+ +
+ + + +

+
+ +

+ +

+
+ +

+ +

+ + \ No newline at end of file diff --git a/application/modules/auth/views/edit_user.php b/application/modules/auth/views/edit_user.php new file mode 100644 index 0000000..8e930f2 --- /dev/null +++ b/application/modules/auth/views/edit_user.php @@ -0,0 +1,67 @@ + +

+

+ +
+ + + +

+
+ +

+ +

+
+ +

+ +

+
+ +

+ +

+
+ +

+ +

+
+ +

+ +

+
+ +

+ + ion_auth->is_admin()): ?> + +

+ + + + + + + id);?> + + +

+ + diff --git a/application/modules/auth/views/email/activate.tpl.php b/application/modules/auth/views/email/activate.tpl.php new file mode 100644 index 0000000..e2436cf --- /dev/null +++ b/application/modules/auth/views/email/activate.tpl.php @@ -0,0 +1,6 @@ + + +

+

+ + \ No newline at end of file diff --git a/application/modules/auth/views/email/forgot_password.tpl.php b/application/modules/auth/views/email/forgot_password.tpl.php new file mode 100644 index 0000000..2cc5b02 --- /dev/null +++ b/application/modules/auth/views/email/forgot_password.tpl.php @@ -0,0 +1,6 @@ + + +

+

+ + \ No newline at end of file diff --git a/application/modules/auth/views/email/new_password.tpl.php b/application/modules/auth/views/email/new_password.tpl.php new file mode 100644 index 0000000..f223986 --- /dev/null +++ b/application/modules/auth/views/email/new_password.tpl.php @@ -0,0 +1,7 @@ + + +

+ +

+ + \ No newline at end of file diff --git a/application/modules/auth/views/forgot_password.php b/application/modules/auth/views/forgot_password.php new file mode 100644 index 0000000..1eb2bde --- /dev/null +++ b/application/modules/auth/views/forgot_password.php @@ -0,0 +1,15 @@ +

+

+ +
+ + + +

+
+ +

+ +

+ + diff --git a/application/modules/auth/views/index.php b/application/modules/auth/views/index.php new file mode 100644 index 0000000..ddaa030 --- /dev/null +++ b/application/modules/auth/views/index.php @@ -0,0 +1,34 @@ + + +

+

+ +
+ + + + + + + + + + + + + + + + + + + + + +
first_name,ENT_QUOTES,'UTF-8');?>last_name,ENT_QUOTES,'UTF-8');?>email,ENT_QUOTES,'UTF-8');?> + groups as $group):?> + id, htmlspecialchars($group->name,ENT_QUOTES,'UTF-8')) ;?>
+ +
active) ? anchor("auth/deactivate/".$user->id, lang('index_active_link')) : anchor("auth/activate/". $user->id, lang('index_inactive_link'));?>id, 'Modifier') ;?>
+ +

| |

\ No newline at end of file diff --git a/application/modules/auth/views/login.php b/application/modules/auth/views/login.php new file mode 100644 index 0000000..e64640a --- /dev/null +++ b/application/modules/auth/views/login.php @@ -0,0 +1,82 @@ + + + + + + + Connexion Tableau de bord + + + + + + + + + +