├── .gitignore ├── classes ├── appform.php ├── auth.php ├── auth │ └── orm.php ├── controller │ ├── admin │ │ └── user.php │ ├── app.php │ └── user.php ├── helper │ ├── datatable.php │ └── format.php ├── message.php ├── model │ ├── user.php │ └── user │ │ └── identity.php ├── provider.php ├── provider │ ├── facebook.php │ ├── oauth.php │ ├── openid.php │ └── twitter.php └── useradmin │ ├── appform.php │ ├── auth.php │ ├── auth │ └── orm.php │ ├── controller │ ├── admin │ │ └── user.php │ ├── app.php │ └── user.php │ ├── driver │ └── iauth.php │ ├── helper │ ├── datatable.php │ └── format.php │ ├── message.php │ ├── model │ ├── user.php │ └── user │ │ └── identity.php │ ├── provider.php │ └── provider │ ├── facebook.php │ ├── oauth.php │ ├── openid.php │ └── twitter.php ├── config ├── facebook.php ├── oauth.php ├── recaptcha.php └── useradmin.php ├── database └── useradmin.sql ├── docs ├── Useradmin_Auth.dia └── Useradmin_Auth.png ├── init.php ├── messages ├── login.php └── register │ └── user.php ├── public ├── css │ └── style.css └── img │ ├── boxbar-background.png │ ├── button-background.png │ ├── facebook.png │ ├── fb-login.png │ ├── google.gif │ ├── linkedin.png │ ├── menubar-background.png │ ├── small │ ├── facebook.png │ ├── facebook_gray.png │ ├── google.png │ ├── google_gray.png │ ├── twitter.png │ ├── twitter_gray.png │ ├── yahoo.png │ └── yahoo_gray.png │ ├── tiny │ ├── facebook.png │ ├── google.png │ ├── twitter.png │ └── yahoo.png │ ├── twitter.png │ └── yahoo.gif ├── readme.md ├── schema.sql ├── tests ├── data │ └── clean-setup.sql └── usertests.php ├── useradmin-screen.png ├── vendor ├── facebook │ └── src │ │ ├── base_facebook.php │ │ ├── facebook.php │ │ └── fb_ca_chain_bundle.crt ├── lightopenid │ └── openid.php └── recaptcha │ ├── LICENSE │ ├── README │ ├── example-captcha.php │ ├── example-mailhide.php │ └── recaptchalib.php └── views ├── pagination └── useradmin.php ├── template ├── default.php └── useradmin.php └── user ├── admin ├── delete.php ├── edit.php └── index.php ├── associate.php ├── login.php ├── noaccess.php ├── profile.php ├── profile_edit.php ├── register.php ├── reset ├── forgot.php └── reset.php └── unregister.php /.gitignore: -------------------------------------------------------------------------------- 1 | /nbproject/private/ 2 | /nbproject/ 3 | .buildpath 4 | .settings 5 | .project 6 | .svn -------------------------------------------------------------------------------- /classes/appform.php: -------------------------------------------------------------------------------- 1 | error) from Validation. 16 | * @var array 17 | */ 18 | public $errors; 19 | 20 | /** 21 | * Set default values (fieldname => default value). In the fields, use NULL if you want to use the default value. 22 | * @var array 23 | */ 24 | public $defaults; 25 | 26 | /** 27 | * Set actual values (fieldname => actual value). 28 | * @var array 29 | */ 30 | public $values; 31 | 32 | /** 33 | * CSS class strings for messages. You can override these. 34 | * @var string 35 | */ 36 | public $info_class = 'info'; 37 | 38 | public $error_class = 'error'; 39 | 40 | /** 41 | * Add a class to the input attributes array. 42 | * @param array $attributes 43 | * @param string $class 44 | * @return array 45 | */ 46 | private static function add_class ($attributes, $class) 47 | { 48 | if (isset($attributes['class'])) 49 | { 50 | $attributes['class'] .= ' ' . $class; 51 | } 52 | else 53 | { 54 | $attributes['class'] = $class; 55 | } 56 | return $attributes; 57 | } 58 | 59 | /** 60 | * Load values for errors, defaults and values from AppForm instance. 61 | * @param $name 62 | * @param $value 63 | * @param $attributes 64 | */ 65 | private function load_values ($name, &$value, &$attributes) 66 | { 67 | if (isset($this->errors[$name])) 68 | { 69 | $attributes = Appform::add_class($attributes, 'error'); 70 | } 71 | if (isset($this->defaults[$name]) && $value == NULL) 72 | { 73 | $value = $this->defaults[$name]; 74 | } 75 | if (isset($this->values[$name]) && $value == NULL) 76 | { 77 | $value = $this->values[$name]; 78 | } 79 | } 80 | 81 | /** 82 | * Add alert span for error or field info 83 | * 84 | * @param string $errorName $this->errors[$name] 85 | * @param string $attrInfo $attributes['info'] 86 | * @return string 87 | */ 88 | private function addAlertSpan($errorName, $attributes = NULL) 89 | { 90 | if (isset($errorName)) 91 | { 92 | $result = '' 93 | . ucfirst($errorName) 94 | . ''; 95 | } 96 | else 97 | { 98 | if (isset($attributes['info'])) 99 | { 100 | // else add info span 101 | $result = '' 102 | . $attributes['info'] 103 | . ''; 104 | } 105 | } 106 | return (string) (isset($result))?$result:''; 107 | } 108 | 109 | /** 110 | * Generates an opening HTML form tag. 111 | * 112 | * @param string form action 113 | * @param array html attributes 114 | * @return string 115 | */ 116 | public function open ($action = NULL, array $attributes = NULL) 117 | { 118 | return Kohana_Form::open($action, $attributes); 119 | } 120 | 121 | /** 122 | * Creates the closing form tag. 123 | * 124 | * @return string 125 | */ 126 | public function close () 127 | { 128 | return Kohana_Form::close(); 129 | } 130 | 131 | /** 132 | * Creates a form input. If no type is specified, a "text" type input will 133 | * be returned. 134 | * 135 | * @param string input name 136 | * @param string input value 137 | * @param array html attributes 138 | * @return string 139 | */ 140 | public function input($name, $value = NULL, array $attributes = NULL) 141 | { 142 | $attributes = Appform::add_class($attributes, 'text'); 143 | $this->load_values($name, $value, $attributes); 144 | return '
  • ' 145 | . Kohana_Form::input($name, $value, $attributes) 146 | . $this->addAlertSpan((isset($this->errors[$name])?$this->errors[$name]:NULL), $attributes) 147 | . '
  • '; 148 | } 149 | 150 | /** 151 | * Creates a hidden form input. 152 | * 153 | * @param string input name 154 | * @param string input value 155 | * @param array html attributes 156 | * @return string 157 | */ 158 | public function hidden($name, $value = NULL, array $attributes = NULL) 159 | { 160 | $this->load_values($name, $value, $attributes); 161 | return Kohana_Form::hidden($name, $value, $attributes); 162 | } 163 | 164 | /** 165 | * Creates a password form input. 166 | * 167 | * @param string input name 168 | * @param string input value 169 | * @param array html attributes 170 | * @return string 171 | */ 172 | public function password($name, $value = NULL, array $attributes = NULL) 173 | { 174 | $attributes = Appform::add_class($attributes, 'password'); 175 | $this->load_values($name, $value, $attributes); 176 | return '
  • ' 177 | . Kohana_Form::password($name, $value, $attributes) 178 | . $this->addAlertSpan((isset($this->errors[$name])?$this->errors[$name]:NULL), $attributes) 179 | . '
  • '; 180 | } 181 | 182 | /** 183 | * Creates a file upload form input. 184 | * 185 | * @param string input name 186 | * @param string input value 187 | * @param array html attributes 188 | * @return string 189 | */ 190 | public function file($name, array $attributes = NULL) 191 | { 192 | $this->load_values($name, $dummy, $attributes); 193 | return '
  • ' 194 | . Kohana_Form::file($name, $attributes) 195 | . $this->addAlertSpan((isset($this->errors[$name])?$this->errors[$name]:NULL), $attributes) 196 | . '
  • '; 197 | } 198 | 199 | /** 200 | * Creates a checkbox form input. 201 | * 202 | * @param string input name 203 | * @param string input value 204 | * @param boolean checked status 205 | * @param array html attributes 206 | * @return string 207 | */ 208 | public function checkbox($name, $value = NULL, $checked = FALSE, array $attributes = NULL) 209 | { 210 | $this->load_values($name, $value, $attributes); 211 | return '
  • ' 212 | . Kohana_Form::checkbox($name, $value, $checked, $attributes) 213 | . $this->addAlertSpan((isset($this->errors[$name])?$this->errors[$name]:NULL), $attributes) 214 | . '
  • '; 215 | } 216 | 217 | /** 218 | * Creates a radio form input. 219 | * 220 | * @param string input name 221 | * @param string input value 222 | * @param boolean checked status 223 | * @param array html attributes 224 | * @return string 225 | */ 226 | public function radio($name, $value = NULL, $checked = FALSE, array $attributes = NULL) 227 | { 228 | $this->load_values($name, $value, $attributes); 229 | return '
  • ' 230 | . Kohana_Form::radio($name, $value, $checked, $attributes) 231 | . $this->addAlertSpan((isset($this->errors[$name])?$this->errors[$name]:NULL), $attributes) 232 | . '
  • '; 233 | } 234 | 235 | /** 236 | * Creates a textarea form input. 237 | * 238 | * @param string textarea name 239 | * @param string textarea body 240 | * @param array html attributes 241 | * @param boolean encode existing HTML characters 242 | * @return string 243 | */ 244 | public function textarea($name, $body = '', array $attributes = NULL, $double_encode = TRUE) 245 | { 246 | $this->load_values($name, $body, $attributes); 247 | return '
  • ' 248 | . Kohana_Form::textarea($name, $body, $attributes, $double_encode) 249 | . $this->addAlertSpan((isset($this->errors[$name])?$this->errors[$name]:NULL), $attributes) 250 | . '
  • '; 251 | } 252 | 253 | /** 254 | * Creates a select form input. 255 | * 256 | * @param string input name 257 | * @param array available options 258 | * @param string selected option 259 | * @param array html attributes 260 | * @return string 261 | */ 262 | public function select($name, array $options = NULL, $selected = NULL, array $attributes = NULL) 263 | { 264 | $this->load_values($name, $selected, $attributes); 265 | return '
  • ' 266 | . Kohana_Form::select($name, $options, $selected, $attributes) 267 | . $this->addAlertSpan((isset($this->errors[$name])?$this->errors[$name]:NULL), $attributes) 268 | . '
  • '; 269 | } 270 | 271 | /** 272 | * Creates a submit form input. 273 | * 274 | * @param string input name 275 | * @param string input value 276 | * @param array html attributes 277 | * @return string 278 | */ 279 | public function submit($name, $value, array $attributes = NULL) 280 | { 281 | return Kohana_Form::submit($name, $value, 282 | Appform::add_class($attributes, 'submit')); 283 | } 284 | 285 | /** 286 | * Creates a button form input. Note that the body of a button is NOT escaped, 287 | * to allow images and other HTML to be used. 288 | * 289 | * @param string input name 290 | * @param string input value 291 | * @param array html attributes 292 | * @return string 293 | */ 294 | public function button($name, $body, array $attributes = NULL) 295 | { 296 | return Kohana_Form::button($name, $body, $attributes); 297 | } 298 | 299 | /** 300 | * Creates a form label. 301 | * 302 | * @param string target input 303 | * @param string label text 304 | * @param array html attributes 305 | * @return string 306 | */ 307 | public function label($input, $text = NULL, array $attributes = NULL) 308 | { 309 | return Kohana_Form::label($input, $text, $attributes); 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /classes/useradmin/auth.php: -------------------------------------------------------------------------------- 1 | load('auth'); 24 | 25 | if ( ! $type = $config->get('driver')) 26 | { 27 | $type = 'file'; 28 | } 29 | 30 | // Set the session class name 31 | $class = 'Auth_'.ucfirst($type); 32 | 33 | $config->set("useradmin", Kohana::$config->load('useradmin.auth') ); 34 | 35 | // Create a new session instance 36 | Auth::$_instance = new $class($config); 37 | } 38 | 39 | return Auth::$_instance; 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /classes/useradmin/auth/orm.php: -------------------------------------------------------------------------------- 1 | where($user->unique_key($username), '=', $username)->find(); 24 | } 25 | 26 | // if there are too many recent failed logins, fail now 27 | if (($this->_config["useradmin"]["max_failed_logins"] > 0) && ($user->failed_login_count >= $this->_config["useradmin"]["max_failed_logins"] ) && (strtotime($user->last_failed_login) > strtotime("-".$this->_config["useradmin"]["login_jail_time"] ) )) 28 | { 29 | // do nothing, and fail (too many failed logins within {login_jail_time} minutes). 30 | return FALSE; 31 | } 32 | // Loads default driver before extend the results 33 | $status = parent::_login($user, $password, $remember); 34 | 35 | if($status) 36 | { 37 | // Successful login 38 | // Reset the login failed count 39 | $user->failed_login_count = 0; 40 | $user->save(); 41 | } 42 | else 43 | { 44 | // Failed login 45 | $user->failed_login_count = $user->failed_login_count+1; 46 | $user->last_failed_login = date("Y-m-d H:i:s"); 47 | // Verify if the user id if valid before save it 48 | if(is_numeric( $user->id ) && $user->id != 0) 49 | { 50 | $user->save(); 51 | } 52 | } 53 | 54 | return $status; 55 | } 56 | 57 | /** 58 | * Register a single user 59 | * Method to register new user by Useradmin Auth module, when you set the 60 | * fields, be sure they must respect the driver rules 61 | * 62 | * @param array $fields An array witch contains the fields to be populate 63 | * @returnboolean Operation final status 64 | * @see Useradmin_Driver_iAuth::register() 65 | */ 66 | public function register($fields) 67 | { 68 | if( ! is_object($fields) ) 69 | { 70 | // Load the user 71 | $user = ORM::factory('user'); 72 | } 73 | else 74 | { 75 | // Check for instanced model 76 | if( $fields instanceof Model_User ) 77 | { 78 | $user = $fields; 79 | } 80 | else 81 | { 82 | throw new Kohana_Exception('Invalid user fields.'); 83 | } 84 | } 85 | try 86 | { 87 | $user->create_user($fields, array( 88 | 'username', 89 | 'password', 90 | 'email', 91 | )); 92 | // Add the login role to the user (add a row to the db) 93 | $login_role = new Model_Role(array('name' =>'login')); 94 | $user->add('roles', $login_role); 95 | } 96 | catch (ORM_Validation_Exception $e) 97 | { 98 | throw $e; 99 | return FALSE; 100 | } 101 | return TRUE; 102 | } 103 | 104 | /** 105 | * Unegister multiple users 106 | * Method to unregister existing user by Useradmin Auth module, when you set the 107 | * Model_User reference for removing a user. 108 | * 109 | * @param mixed $users An array witch contains the Model_User or a array of Model_User 110 | * @return void 111 | * @see Useradmin_Driver_iAuth::unregister() 112 | */ 113 | public function unregister ($users) 114 | { 115 | if( ! is_array($users)) 116 | $users = array($users); 117 | 118 | foreach ($users as $user) 119 | { 120 | if($user instanceof Model_User) 121 | { 122 | try 123 | { 124 | $user->delete(); 125 | } 126 | catch (ORM_Validation_Exception $e) 127 | { 128 | throw $e; 129 | } 130 | } 131 | elseif( ! is_null($user) ) 132 | { 133 | throw new Kohana_Exception("Invalid argument, must be instance of Model_User or array() containing Model_User's"); 134 | } 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /classes/useradmin/controller/admin/user.php: -------------------------------------------------------------------------------- 1 | 'admin' will only allow users with the role admin to access action_adminpanel 32 | * 'moderatorpanel' => array('login', 'moderator') will only allow users with the roles login and moderator to access action_moderatorpanel 33 | */ 34 | public $secure_actions = array(); 35 | 36 | // USER ADMINISTRATION 37 | /** 38 | * Administator view of users. 39 | */ 40 | public function action_index() 41 | { 42 | // set the template title (see Controller_App for implementation) 43 | $this->template->title = __('User administration'); 44 | // create a user 45 | $user = ORM::factory('user'); 46 | // This is an example of how to use Kohana pagination 47 | // Get the total count for the pagination 48 | $total = $user->count_all(); 49 | // Create a paginator 50 | $pagination = new Pagination(array( 51 | 'total_items' => $total, 52 | 'items_per_page' => 30, // set this to 30 or 15 for the real thing, now just for testing purposes... 53 | 'auto_hide' => false, 54 | 'view' => 'pagination/useradmin' 55 | )); 56 | // Get the items for the query 57 | $sort = isset($_GET['sort']) ? $_GET['sort'] : 'username'; // set default sorting direction here 58 | $dir = isset($_GET['dir']) ? 'DESC' : 'ASC'; 59 | $result = $user->limit($pagination->items_per_page) 60 | ->offset($pagination->offset) 61 | ->order_by($sort, $dir) 62 | ->find_all(); 63 | // render view 64 | // pass the paginator, result and default sorting direction 65 | $this->template->content = View::factory('user/admin/index') 66 | ->set('users', $result) 67 | ->set('paging', $pagination) 68 | ->set('default_sort', $sort); 69 | } 70 | 71 | /** 72 | * Administrator edit user. 73 | * @param string $id 74 | * @return void 75 | */ 76 | public function action_edit($id = NULL) 77 | { 78 | // set the template title (see Controller_App for implementation) 79 | $this->template->title = __('Edit user'); 80 | // load the content from view 81 | $view = View::factory('user/admin/edit'); 82 | // save the data 83 | if (! empty($_POST)) 84 | { 85 | //FIXME: Use Model_User in the controller insteat ORM::factory() for model generic driver compatibility 86 | // sample code paths for edit and create 87 | if (is_numeric($id)) 88 | { 89 | // EDIT: load the model with ID 90 | $user = ORM::factory('user', $id); 91 | } 92 | else 93 | { 94 | // CREATE: do not specify id 95 | $user = ORM::factory('user'); 96 | } 97 | if (empty($_POST['password']) || empty($_POST['password_confirm'])) 98 | { 99 | // force unsetting the password! Otherwise Kohana3 will automatically hash the empty string - preventing logins 100 | unset($_POST['password'], $_POST['password_confirm']); 101 | } 102 | // you can't change your user id 103 | unset($_POST['id']); 104 | $user->values($_POST); 105 | // since we combine both editing and creating here we need a separate variable 106 | // you can get rid of it if your actions don't need to do that 107 | $result = false; 108 | $errors = null; 109 | if (is_numeric($id)) 110 | { 111 | // EDIT: check using alternative rules 112 | try 113 | { 114 | $user->update_user($_POST, array( 115 | 'username', 116 | 'password', 117 | 'email' 118 | )); 119 | $result = true; 120 | } 121 | catch (ORM_Validation_Exception $e) 122 | { 123 | $errors = $e->errors('register'); 124 | $errors = array_merge($errors, ( isset($errors['_external']) ? $errors['_external'] : array() )); 125 | } 126 | } 127 | else 128 | { 129 | // CREATE: check using default rules 130 | try 131 | { 132 | $user->create_user($_POST, array( 133 | 'username', 134 | 'password', 135 | 'email' 136 | )); 137 | $result = true; 138 | } 139 | catch (ORM_Validation_Exception $e) 140 | { 141 | $errors = $e->errors('register'); 142 | $errors = array_merge($errors, ( isset($errors['_external']) ? $errors['_external'] : array() )); 143 | } 144 | } 145 | if ($result) 146 | { 147 | // roles have to be added separately, and all users have to have the login role 148 | // you first have to remove the items, otherwise add() will try to add duplicates 149 | if (is_numeric($id)) 150 | { 151 | // could also use array_diff, but this is much simpler 152 | DB::delete('roles_users')->where('user_id', '=', $id) 153 | ->execute(); 154 | } 155 | foreach ($_POST['roles'] as $role) 156 | { 157 | // add() executes the query immediately, and saves the data (unlike the KO2 docs say) 158 | $user->add('roles', 159 | ORM::factory('role')->where('name', '=', $role) 160 | ->find() 161 | ); 162 | } 163 | // message: save success 164 | Message::add('success', __('Values saved.')); 165 | // redirect and exit 166 | Request::current()->redirect('admin_user/index'); 167 | return; 168 | } 169 | else 170 | { 171 | // Get errors for display in view --> to AppForm 172 | Message::add('error', __('Error: Values could not be saved.')); 173 | // Note how the first param is the path to the message file (e.g. /messages/register.php) 174 | $view->set('errors', $errors); 175 | // Pass on the old form values --> to AppForm 176 | $view->set('data', $user->as_array()); 177 | } 178 | } 179 | // if an ID is set, load the information 180 | if (is_numeric($id)) 181 | { 182 | // instantiatiate a new model 183 | $user = ORM::factory('user', $id); 184 | $view->set('data', $user->as_array()); 185 | // retrieve roles into array 186 | $roles = array(); 187 | foreach ($user->roles->find_all() as $role) 188 | { 189 | $roles[] = $role->name; 190 | } 191 | $view->set('user_roles', $roles); 192 | } 193 | else 194 | { 195 | $view->set('user_roles', array( 196 | 'login' => 'login' 197 | )); 198 | } 199 | // get all roles 200 | $all_roles = array(); 201 | $role_model = ORM::factory('role'); 202 | foreach ($role_model->find_all() as $role) 203 | { 204 | $all_roles[$role->name] = $role->description; 205 | } 206 | $view->set('all_roles', $all_roles); 207 | $view->set('id', $id); 208 | $this->template->content = $view; 209 | } 210 | 211 | /** 212 | * Administrator delete user 213 | * @param string $id 214 | * @return void 215 | */ 216 | public function action_delete($id = NULL) 217 | { 218 | // set the template title (see Controller_App for implementation) 219 | $this->template->title = __('Delete user'); 220 | $user = ORM::factory('user', $id); 221 | // check for confirmation 222 | if (is_numeric($id) && isset($_POST['confirmation']) && $_POST['confirmation'] == 'Y') 223 | { 224 | if ($user->loaded()) 225 | { 226 | // Delete the user 227 | $user->delete($id); 228 | // Delete any associated identities 229 | DB::delete('user_identity')->where('user_id', '=', $id) 230 | ->execute(); 231 | // message: save success 232 | Message::add('success', __('User deleted.')); 233 | } 234 | else 235 | { 236 | Message::add('success', __('User is already deleted.')); 237 | } 238 | // redirect and exit 239 | Request::current()->redirect('admin_user/index'); 240 | return; 241 | } 242 | // display confirmation 243 | $this->template->content = View::factory('user/admin/delete') 244 | ->set('id', $id) 245 | ->set('data',array('username' => $user->username)); 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /classes/useradmin/controller/app.php: -------------------------------------------------------------------------------- 1 | template->content to your content, without needing to worry about the containing template. 22 | * 23 | **/ 24 | public $auto_render = TRUE; 25 | 26 | /** 27 | * Controls access for the whole controller, if not set to FALSE we will only allow user roles specified 28 | * 29 | * Can be set to a string or an array, for example array('login', 'admin') or 'login' 30 | */ 31 | public $auth_required = FALSE; 32 | 33 | /** Controls access for separate actions 34 | * 35 | * Examples: 36 | * 'adminpanel' => 'admin' will only allow users with the role admin to access action_adminpanel 37 | * 'moderatorpanel' => array('login', 'moderator') will only allow users with the roles login and moderator to access action_moderatorpanel 38 | */ 39 | public $secure_actions = FALSE; 40 | 41 | protected $session; 42 | 43 | /** 44 | * Called from before() when the user does not have the correct rights to access a controller/action. 45 | * 46 | * Override this in your own Controller / Controller_App if you need to handle 47 | * responses differently. 48 | * 49 | * For example: 50 | * - handle JSON requests by returning a HTTP error code and a JSON object 51 | * - redirect to a different failure page from one part of the application 52 | */ 53 | public function access_required() 54 | { 55 | $this->request->redirect('user/noaccess'); 56 | } 57 | 58 | /** 59 | * Called from before() when the user is not logged in but they should. 60 | * 61 | * Override this in your own Controller / Controller_App. 62 | */ 63 | public function login_required() 64 | { 65 | Request::current()->redirect('user/login'); 66 | } 67 | 68 | /** 69 | * The before() method is called before your controller action. 70 | * In our template controller we override this method so that we can 71 | * set up default values. These variables are then available to our 72 | * controllers if they need to be modified. 73 | * 74 | * @return void 75 | */ 76 | public function before() 77 | { 78 | // This codeblock is very useful in development sites: 79 | // What it does is get rid of invalid sessions which cause exceptions, which may happen 80 | // 1) when you make errors in your code. 81 | // 2) when the session expires! 82 | try 83 | { 84 | $this->session = Session::instance(); 85 | } 86 | catch (ErrorException $e) 87 | { 88 | session_destroy(); 89 | } 90 | // Execute parent::before first 91 | parent::before(); 92 | // Open session 93 | $this->session = Session::instance(); 94 | 95 | //if we're not logged in, but auth type is orm. gives us chance to auto login 96 | $supports_auto_login = new ReflectionClass(get_class(Auth::instance())); 97 | $supports_auto_login = $supports_auto_login->hasMethod('auto_login'); 98 | if(!Auth::instance()->logged_in() && $supports_auto_login){ 99 | Auth::instance()->auto_login(); 100 | } 101 | 102 | // Check user auth and role 103 | $action_name = Request::current()->action(); 104 | if 105 | ( 106 | // auth is required AND user role given in auth_required is NOT logged in 107 | ( $this->auth_required !== FALSE && Auth::instance()->logged_in($this->auth_required) === FALSE ) || 108 | // OR secure_actions is set AND the user role given in secure_actions is NOT logged in 109 | ( is_array($this->secure_actions) && array_key_exists($action_name, $this->secure_actions) && Auth::instance()->logged_in($this->secure_actions[$action_name]) === FALSE ) 110 | ) 111 | { 112 | if (Auth::instance()->logged_in()) 113 | { 114 | // user is logged in but not on the secure_actions list 115 | $this->access_required(); 116 | } 117 | else 118 | { 119 | $this->login_required(); 120 | } 121 | } 122 | if ($this->auto_render) 123 | { 124 | // only load the template if the template has not been set.. 125 | $this->template = View::factory($this->template); 126 | // Initialize empty values 127 | // Page title 128 | $this->template->title = ''; 129 | // Page content 130 | $this->template->content = ''; 131 | // Styles in header 132 | $this->template->styles = array(); 133 | // Scripts in header 134 | $this->template->scripts = array(); 135 | // ControllerName will contain the name of the Controller in the Template 136 | $this->template->controllerName = $this->request->controller(); 137 | // ActionName will contain the name of the Action in the Template 138 | $this->template->actionName = $this->request->action(); 139 | // next, it is expected that $this->template->content is set e.g. by rendering a view into it. 140 | } 141 | } 142 | 143 | /** 144 | * The after() method is called after your controller action. 145 | * In our template controller we override this method so that we can 146 | * make any last minute modifications to the template before anything 147 | * is rendered. 148 | */ 149 | public function after() 150 | { 151 | if ($this->auto_render === TRUE) 152 | { 153 | $styles = array( 154 | 'css/style.css' => 'screen' 155 | ); 156 | $scripts = array(); 157 | $this->template->styles = array_merge($this->template->styles, $styles); 158 | $this->template->scripts = array_merge($this->template->scripts, $scripts); 159 | 160 | // Display profile if its enabled and request by query profile 161 | $this->template->profile = (isset($_REQUEST['profile']) && Kohana::$profiling)?"
    ".View::factory('profiler/stats')."
    ":""; 162 | 163 | // Assign the template as the request response and render it 164 | $this->response->body($this->template); 165 | } 166 | parent::after(); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /classes/useradmin/controller/user.php: -------------------------------------------------------------------------------- 1 | 'admin' will only allow users with the role admin to access action_adminpanel 32 | * 'moderatorpanel' => array('login', 'moderator') will only allow users with the roles login and moderator to access action_moderatorpanel 33 | */ 34 | public $secure_actions = array( 35 | // user actions 36 | 'index' => 'login', 37 | 'profile' => 'login', 38 | 'profile_edit' => 'login', 39 | 'unregister' => 'login', 40 | 'change_password' => 'login' 41 | ); // the others are public (forgot, login, register, reset, noaccess) 42 | // logout is also public to avoid confusion (e.g. easier to specify and test post-logout page) 43 | 44 | public function before(){ 45 | $baseUrl = Url::base(true); 46 | if(substr($this->request->referrer(),0,strlen($baseUrl)) == $baseUrl){ 47 | $urlPath = ltrim(parse_url($this->request->referrer(),PHP_URL_PATH),'/'); 48 | $processedRef = Request::process_uri($urlPath); 49 | $referrerController = Arr::path( 50 | $processedRef, 51 | 'params.controller', 52 | false 53 | ); 54 | if($referrerController && $referrerController != 'user' && !Session::instance()->get('noReturn',false)){ 55 | Session::instance()->set('returnUrl',$this->request->referrer()); 56 | } 57 | 58 | } 59 | 60 | parent::before(); 61 | 62 | } 63 | 64 | // USER SELF-MANAGEMENT 65 | /** 66 | * View: Redirect admins to admin index, users to user profile. 67 | */ 68 | public function action_index() 69 | { 70 | // if the user has the admin role, redirect to admin_user controller 71 | if (Auth::instance()->logged_in('admin')) 72 | { 73 | $this->request->redirect('admin_user/index'); 74 | } 75 | else 76 | { 77 | $this->request->redirect('user/profile'); 78 | } 79 | } 80 | 81 | /** 82 | * View: Access not allowed. 83 | */ 84 | public function action_noaccess() 85 | { 86 | // set the template title (see Controller_App for implementation) 87 | $this->template->title = __('Access not allowed'); 88 | $view = $this->template->content = View::factory('user/noaccess'); 89 | } 90 | 91 | /** 92 | * View: User account information 93 | */ 94 | public function action_profile() 95 | { 96 | // set the template title (see Controller_App for implementation) 97 | $this->template->title = __('User profile'); 98 | if (Auth::instance()->logged_in() == false) 99 | { 100 | // No user is currently logged in 101 | $this->request->redirect('user/login'); 102 | } 103 | $view = $this->template->content = View::factory('user/profile'); 104 | // retrieve the current user and set the view variable accordingly 105 | $view->set('user', Auth::instance()->get_user()); 106 | } 107 | 108 | /** 109 | * View: Profile editor 110 | */ 111 | public function action_profile_edit() 112 | { 113 | // set the template title (see Controller_App for implementation) 114 | $this->template->title = __('Edit user profile'); 115 | $user = Auth::instance()->get_user(); 116 | $id = $user->id; 117 | // load the content from view 118 | $view = View::factory('user/profile_edit'); 119 | // save the data 120 | if (! empty($_POST) && is_numeric($id)) 121 | { 122 | if (empty($_POST['password']) || empty($_POST['password_confirm'])) 123 | { 124 | // force unsetting the password! Otherwise Kohana3 will automatically hash the empty string - preventing logins 125 | unset($_POST['password'], $_POST['password_confirm']); 126 | } 127 | try 128 | { 129 | $user->update_user($_POST, 130 | array( 131 | 'username', 132 | 'password', 133 | 'email' 134 | )); 135 | // message: save success 136 | Message::add('success', __('Values saved.')); 137 | // redirect and exit 138 | $this->request->redirect('user/profile'); 139 | return; 140 | } 141 | catch (ORM_Validation_Exception $e) 142 | { 143 | // Get errors for display in view 144 | // Note how the first param is the path to the message file (e.g. /messages/register.php) 145 | Message::add('error', __('Error: Values could not be saved.')); 146 | $errors = $e->errors('register'); 147 | $errors = array_merge($errors, ( isset($errors['_external']) ? $errors['_external'] : array() )); 148 | $view->set('errors', $errors); 149 | // Pass on the old form values 150 | $user->password = ''; 151 | $view->set('data', $user->as_array()); 152 | } 153 | } 154 | else 155 | { 156 | // load the information for viewing 157 | $view->set('data', $user->as_array()); 158 | } 159 | // retrieve roles into array 160 | $roles = array(); 161 | foreach ($user->roles->find_all() as $role) 162 | { 163 | $roles[$role->name] = $role->description; 164 | } 165 | $view->set('user_roles', $roles); 166 | $view->set('id', $id); 167 | $this->template->content = $view; 168 | } 169 | 170 | /** 171 | * Register a new user. 172 | */ 173 | public function action_register() 174 | { 175 | // Load reCaptcha if needed 176 | if (Kohana::$config->load('useradmin')->captcha) 177 | { 178 | include Kohana::find_file('vendor', 'recaptcha/recaptchalib'); 179 | $recaptcha_config = Kohana::$config->load('recaptcha'); 180 | $recaptcha_error = null; 181 | } 182 | // set the template title (see Controller_App for implementation) 183 | $this->template->title = __('User registration'); 184 | // If user already signed-in 185 | if (Auth::instance()->logged_in() != false) 186 | { 187 | // redirect to the user account 188 | $this->request->redirect('user/profile'); 189 | } 190 | // Load the view 191 | $view = View::factory('user/register'); 192 | // If there is a post and $_POST is not empty 193 | if ($_POST) 194 | { 195 | // optional checks (e.g. reCaptcha or some other additional check) 196 | $optional_checks = true; 197 | // if configured to use captcha, check the reCaptcha result 198 | if (Kohana::$config->load('useradmin')->captcha) 199 | { 200 | $recaptcha_resp = recaptcha_check_answer( 201 | $recaptcha_config['privatekey'], 202 | $_SERVER['REMOTE_ADDR'], 203 | $_POST['recaptcha_challenge_field'], 204 | $_POST['recaptcha_response_field'] 205 | ); 206 | if (! $recaptcha_resp->is_valid) 207 | { 208 | $optional_checks = false; 209 | $recaptcha_error = $recaptcha_resp->error; 210 | Message::add('error', __('The captcha text is incorrect, please try again.')); 211 | } 212 | } 213 | try 214 | { 215 | if (! $optional_checks) 216 | { 217 | throw new ORM_Validation_Exception("Invalid option checks"); 218 | } 219 | Auth::instance()->register($_POST, TRUE); 220 | // sign the user in 221 | Auth::instance()->login($_POST['username'], $_POST['password']); 222 | // redirect to the user account 223 | $this->request->redirect(Session::instance()->get_once('returnUrl','user/profile')); 224 | } 225 | catch (ORM_Validation_Exception $e) 226 | { 227 | // Get errors for display in view 228 | // Note how the first param is the path to the message file (e.g. /messages/register.php) 229 | $errors = $e->errors('register'); 230 | // Move external errors to main array, for post helper compatibility 231 | $errors = array_merge($errors, ( isset($errors['_external']) ? $errors['_external'] : array() )); 232 | $view->set('errors', $errors); 233 | // Pass on the old form values 234 | $_POST['password'] = $_POST['password_confirm'] = ''; 235 | $view->set('defaults', $_POST); 236 | } 237 | } 238 | if (Kohana::$config->load('useradmin')->captcha) 239 | { 240 | $view->set('captcha_enabled', true); 241 | $view->set('recaptcha_html', recaptcha_get_html($recaptcha_config['publickey'], $recaptcha_error)); 242 | } 243 | $this->template->content = $view; 244 | } 245 | 246 | /** 247 | * Close the current user's account. 248 | */ 249 | public function action_unregister() 250 | { 251 | // set the template title (see Controller_App for implementation) 252 | $this->template->title = __('Close user account'); 253 | if (Auth::instance()->logged_in() == false) 254 | { 255 | // No user is currently logged in 256 | $this->request->redirect('user/login'); 257 | } 258 | // get the user id 259 | $id = Auth::instance()->get_user()->id; 260 | $user = ORM::factory('user', $id); 261 | // KO3 ORM is lazy loading, which means we have to access a single field to actually have something happen. 262 | if ($user->id != $id) 263 | { 264 | // If the user is not the current user, redirect 265 | $this->request->redirect('user/profile'); 266 | } 267 | // check for confirmation 268 | if (is_numeric($id) && isset($_POST['confirmation']) && $_POST['confirmation'] == 'Y') 269 | { 270 | if (Auth::instance()->logged_in()) 271 | { 272 | // Log the user out, their account will no longer exist 273 | Auth::instance()->logout(); 274 | } 275 | // Delete the user 276 | $user->delete($id); 277 | // Delete any associated identities 278 | DB::delete('user_identity')->where('user_id', '=', $id) 279 | ->execute(); 280 | // message: save success 281 | Message::add('success', __('User deleted.')); 282 | $this->request->redirect(Session::instance()->get_once('returnUrl','user/profile')); 283 | } 284 | // display confirmation 285 | $this->template->content = View::factory('user/unregister') 286 | ->set('id', $id) 287 | ->set('data', array('username' => Auth::instance()->get_user()->username)); 288 | } 289 | 290 | /** 291 | * View: Login form. 292 | */ 293 | public function action_login() 294 | { 295 | // ajax login 296 | if ($this->request->is_ajax() && isset($_REQUEST['username'], $_REQUEST['password'])) 297 | { 298 | $this->auto_render = false; 299 | $this->request->headers('Content-Type', 'application/json'); 300 | if (Auth::instance()->logged_in() != 0) 301 | { 302 | $this->response->status(200); 303 | $this->template->content = $this->request->body('{ "success": "true" }'); 304 | return; 305 | } 306 | else { 307 | if (Auth::instance()->login($_REQUEST['username'],$_REQUEST['password'], 308 | Arr::get($_REQUEST,'remember',false)!=false) 309 | ){ 310 | $this->response->status(200); 311 | $this->template->content = $this->request->body('{ "success": "true" }'); 312 | return; 313 | } 314 | } 315 | $this->response->status(500); 316 | $this->template->content = $this->request->body('{ "success": "false" }'); 317 | return; 318 | } 319 | else 320 | { 321 | // set the template title (see Controller_App for implementation) 322 | $this->template->title = __('Login'); 323 | // If user already signed-in 324 | if (Auth::instance()->logged_in() != 0) 325 | { 326 | // redirect to the user account 327 | $this->request->redirect(Session::instance()->get_once('returnUrl','user/profile')); 328 | } 329 | $view = View::factory('user/login'); 330 | // If there is a post and $_POST is not empty 331 | if ($_REQUEST && isset($_REQUEST['username'], $_REQUEST['password'])) 332 | { 333 | // Check Auth if the post data validates using the rules setup in the user model 334 | if (Auth::instance()->login($_REQUEST['username'], $_REQUEST['password'], 335 | Arr::get($_REQUEST,'remember',false)!=false) 336 | ){ 337 | // redirect to the user account 338 | $this->request->redirect(Session::instance()->get_once('returnUrl','user/profile')); 339 | return; 340 | } 341 | else 342 | { 343 | $view->set('username', $_REQUEST['username']); 344 | // Get errors for display in view 345 | $validation = Validation::factory($_REQUEST) 346 | ->rule('username', 'not_empty') 347 | ->rule('password', 'not_empty'); 348 | if ($validation->check()) 349 | { 350 | $validation->error('password', 'invalid'); 351 | } 352 | $view->set('errors', $validation->errors('login')); 353 | } 354 | } 355 | // allow setting the username as a get param 356 | if (isset($_GET['username'])) 357 | { 358 | $view->set('username', htmlspecialchars($_GET['username'])); 359 | } 360 | $providers = Kohana::$config->load('useradmin.providers'); 361 | $view->set('facebook_enabled', 362 | isset($providers['facebook']) ? $providers['facebook'] : false); 363 | $this->template->content = $view; 364 | } 365 | } 366 | 367 | /** 368 | * Log the user out. 369 | */ 370 | public function action_logout() 371 | { 372 | // Sign out the user 373 | Auth::instance()->logout(); 374 | // redirect to the user account and then the signin page if logout worked as expected 375 | $this->request->redirect(Session::instance()->get_once('returnUrl','user/profile')); 376 | } 377 | 378 | /** 379 | * A basic implementation of the "Forgot password" functionality 380 | */ 381 | public function action_forgot() 382 | { 383 | // Password reset must be enabled in config/useradmin.php 384 | if (! Kohana::$config->load('useradmin')->email) 385 | { 386 | Message::add('error', 'Password reset via email is not enabled. Please contact the site administrator to reset your password.'); 387 | $this->request->redirect('user/register'); 388 | } 389 | // set the template title (see Controller_App for implementation) 390 | $this->template->title = __('Forgot password'); 391 | if (isset($_POST['reset_email'])) 392 | { 393 | $user = ORM::factory('user')->where('email', '=', $_POST['reset_email'])->find(); 394 | // admin passwords cannot be reset by email 395 | if (is_numeric($user->id) && ( $user->username != 'admin' )) 396 | { 397 | // send an email with the account reset token 398 | $user->reset_token = $user->generate_password(32); 399 | $user->save(); 400 | $message = "You have requested a password reset. You can reset password to your account by visiting the page at:\n\n" . 401 | ":reset_token_link\n\n" . 402 | "If the above link is not clickable, please visit the following page:\n" . 403 | ":reset_link\n\n" . 404 | "and copy/paste the following Reset Token: :reset_token\nYour user account name is: :username\n"; 405 | $mailer = Email::connect(); 406 | // Create complex Swift_Message object stored in $message 407 | // MUST PASS ALL PARAMS AS REFS 408 | $subject = __('Account password reset'); 409 | $to = $_POST['reset_email']; 410 | $from = Kohana::$config->load('useradmin')->email_address; 411 | $body = __($message, array( 412 | ':reset_token_link' => URL::site('user/reset?reset_token='.$user->reset_token.'&reset_email='.$_POST['reset_email'], TRUE), 413 | ':reset_link' => URL::site('user/reset', TRUE), 414 | ':reset_token' => $user->reset_token, 415 | ':username' => $user->username 416 | )); 417 | // FIXME: Test if Swift_Message has been found. 418 | $message_swift = Swift_Message::newInstance($subject, $body)->setFrom($from)->setTo($to); 419 | if ($mailer->send($message_swift)) 420 | { 421 | Message::add('success', __('Password reset email sent.')); 422 | $this->request->redirect('user/login'); 423 | } 424 | else 425 | { 426 | Message::add('failure', __('Could not send email.')); 427 | } 428 | } 429 | else 430 | if ($user->username == 'admin') 431 | { 432 | Message::add('error', __('Admin account password cannot be reset via email.')); 433 | } 434 | else 435 | { 436 | Message::add('error', __('User account could not be found.')); 437 | } 438 | } 439 | $this->template->content = View::factory('user/reset/forgot'); 440 | } 441 | 442 | /** 443 | * A basic version of "reset password" functionality. 444 | */ 445 | function action_reset() 446 | { 447 | // Password reset must be enabled in config/useradmin.php 448 | if (! Kohana::$config->load('useradmin')->email) 449 | { 450 | Message::add('error', 'Password reset via email is not enabled. Please contact the site administrator to reset your password.'); 451 | $this->request->redirect('user/register'); 452 | } 453 | // set the template title (see Controller_App for implementation) 454 | $this->template->title = __('Reset password'); 455 | if (isset($_REQUEST['reset_token']) && isset($_REQUEST['reset_email'])) 456 | { 457 | // make sure that the reset_token has exactly 32 characters (not doing that would allow resets with token length 0) 458 | if (( strlen($_REQUEST['reset_token']) == 32 ) && ( strlen(trim($_REQUEST['reset_email'])) > 1 )) 459 | { 460 | $user = ORM::factory('user') 461 | ->where('email', '=', $_REQUEST['reset_email']) 462 | ->and_where('reset_token', '=', $_REQUEST['reset_token']) 463 | ->find(); 464 | // The admin password cannot be reset by email 465 | if ($user->has('roles',ORM::factory('role',array('name'=>'admin')))) 466 | { 467 | Message::add('failure', __('The admin password cannot be reset by email.')); 468 | } 469 | else 470 | if (is_numeric($user->id) && ( $user->reset_token == $_REQUEST['reset_token'] )) 471 | { 472 | $password = $user->generate_password(); 473 | $user->password = $password; 474 | // This field does not exist in the default config: 475 | // $user->failed_login_count = 0; 476 | $user->save(); 477 | Message::add('success', __('Password reset.')); 478 | Message::add('success', '

    ' 479 | . __('Your password has been reset to: ":password".', array(':password' => $password)) 480 | . '

    ' 481 | . __('Please log in below.') 482 | . '

    ' 483 | ); 484 | $this->request->redirect('user/login?username=' . $user->username); 485 | } 486 | } 487 | } 488 | $this->template->content = View::factory('user/reset/reset'); 489 | } 490 | 491 | /** 492 | * Allow the user to change their password. 493 | */ 494 | function action_change_password() 495 | { 496 | // set the template title (see Controller_App for implementation) 497 | $this->template->title = __('Change password'); 498 | $user = Auth::instance()->get_user(); 499 | $id = $user->id; 500 | // load the content from view 501 | $view = View::factory('user/change_password'); 502 | // save the data 503 | if (! empty($_POST) && is_numeric($id)) 504 | { 505 | // editing requires that the username and email do not exist (EXCEPT for this ID) 506 | // If the post data validates using the rules setup in the user model 507 | $param_by_ref = array( 508 | 'password' => $_POST['password'], 509 | 'password_confirm' => $_POST['password_confirm'] 510 | ); 511 | $validate = $user->change_password($param_by_ref, FALSE); 512 | if ($validate) 513 | { 514 | // message: save success 515 | Message::add('success', __('Values saved.')); 516 | // redirect and exit 517 | $this->request->redirect('user/index'); //index will redir ya whereever you need 518 | return; 519 | } 520 | else 521 | { 522 | // UNFORTUNATELY, it is NOT possible to get errors for display in view 523 | // since they will never be returned by change_password() 524 | Message::add('error', __('Password could not be changed, please make sure that the passwords match.')); 525 | // Pass on the old form values 526 | $_POST['password'] = $_POST['password_confirm'] = ''; 527 | $view->set('defaults', $_POST); 528 | } 529 | } 530 | else 531 | { 532 | // load the information for viewing 533 | $view->set('data', $user->as_array()); 534 | } 535 | $this->template->content = $view; 536 | } 537 | 538 | /** 539 | * Redirect to the provider's auth URL 540 | * @param string $provider 541 | */ 542 | function action_provider ($provider_name = null) 543 | { 544 | if (Auth::instance()->logged_in()) 545 | { 546 | Message::add('success', 'Already logged in.'); 547 | // redirect to the user account 548 | $this->request->redirect('user/profile'); 549 | } 550 | $provider = Provider::factory($provider_name); 551 | if ($this->request->query('code') && $this->request->query('state')) 552 | { 553 | $this->action_provider_return($provider_name); 554 | return; 555 | } 556 | if (is_object($provider)) 557 | { 558 | $this->request->redirect( 559 | $provider->redirect_url('/user/provider_return/' . $provider_name)); 560 | return; 561 | } 562 | Message::add('error', 'Provider is not enabled; please select another provider or log in normally.'); 563 | $this->request->redirect('user/login'); 564 | return; 565 | } 566 | 567 | function action_associate($provider_name = null) 568 | { 569 | if ($this->request->query('code') && $this->request->query('state')) 570 | { 571 | $this->action_associate_return($provider_name); 572 | return; 573 | } 574 | if (Auth::instance()->logged_in()) 575 | { 576 | if (isset($_POST['confirmation']) && $_POST['confirmation'] == 'Y') 577 | { 578 | $provider = Provider::factory($provider_name); 579 | if (is_object($provider)) 580 | { 581 | $this->request->redirect($provider->redirect_url('/user/associate_return/' . $provider_name)); 582 | return; 583 | } 584 | else 585 | { 586 | Message::add('error', 'Provider is not enabled; please select another provider or log in normally.'); 587 | $this->request->redirect('user/login'); 588 | return; 589 | } 590 | } 591 | else 592 | if (isset($_POST['confirmation'])) 593 | { 594 | Message::add('error', 'Please click Yes to confirm associating the account.'); 595 | $this->request->redirect('user/profile'); 596 | return; 597 | } 598 | } 599 | else 600 | { 601 | Message::add('error', 'You are not logged in.'); 602 | $this->request->redirect('user/login'); 603 | return; 604 | } 605 | $this->template->content = View::factory('user/associate')->set('provider_name', $provider_name); 606 | } 607 | 608 | /** 609 | * Associate a logged in user with an account. 610 | * 611 | * Note that you should not trust the OAuth/OpenID provider-supplied email 612 | * addresses. Yes, for Facebook, Twitter, Google and Yahoo the user is actually 613 | * required to ensure that the email is in fact one that they control. 614 | * 615 | * However, with generic OpenID (and non-trusted OAuth providers) one can setup a 616 | * rogue provider that claims the user owns a particular email address without 617 | * actually owning it. So if you trust the email information, then you open yourself to 618 | * a vulnerability since someone might setup a provider that claims to own your 619 | * admin account email address and if you don't require the user to log in to 620 | * associate their account they gain access to any account. 621 | * 622 | * TL;DR - the only information you can trust is that the identity string is 623 | * associated with that user on that openID provider, you need the user to also 624 | * prove that they want to trust that identity provider on your application. 625 | * 626 | */ 627 | function action_associate_return($provider_name = null) 628 | { 629 | if (Auth::instance()->logged_in()) 630 | { 631 | $provider = Provider::factory($provider_name); 632 | // verify the request 633 | if (is_object($provider) && $provider->verify()) 634 | { 635 | $user = Auth::instance()->get_user(); 636 | if ($user->loaded() && is_numeric($user->id)) 637 | { 638 | if (Auth::instance()->logged_in() && Auth::instance()->get_user()->id == $user->id) 639 | { 640 | // found: "merge" with the existing user 641 | $user_identity = ORM::factory('user_identity'); 642 | $user_identity->user_id = $user->id; 643 | $user_identity->provider = $provider_name; 644 | $user_identity->identity = $provider->user_id(); 645 | if ($user_identity->check()) 646 | { 647 | Message::add('success', __('Your user account has been associated with this provider.')); 648 | $user_identity->save(); 649 | // redirect to the user account 650 | $this->request->redirect('user/profile'); 651 | return; 652 | } 653 | else 654 | { 655 | Message::add('error', 'We were unable to associate this account with the provider. Please make sure that there are no other accounts using this provider identity, as each 3rd party provider identity can only be associated with one user account.'); 656 | $this->request->redirect('user/login'); 657 | return; 658 | } 659 | } 660 | } 661 | } 662 | } 663 | Message::add('error', 'There was an error associating your account with this provider.'); 664 | $this->request->redirect('user/login'); 665 | return; 666 | } 667 | 668 | /** 669 | * Allow the user to login and register using a 3rd party provider. 670 | */ 671 | function action_provider_return($provider_name = null) 672 | { 673 | $provider = Provider::factory($provider_name); 674 | if (! is_object($provider)) 675 | { 676 | Message::add('error', 'Provider is not enabled; please select another provider or log in normally.'); 677 | $this->request->redirect('user/login'); 678 | return; 679 | } 680 | // verify the request 681 | if ($provider->verify()) 682 | { 683 | // check for previously connected user 684 | $uid = $provider->user_id(); 685 | $user_identity = ORM::factory('user_identity') 686 | ->where('provider', '=', $provider_name) 687 | ->and_where('identity', '=', $uid) 688 | ->find(); 689 | if ($user_identity->loaded()) 690 | { 691 | $user = $user_identity->user; 692 | if ($user->loaded() && $user->id == $user_identity->user_id && is_numeric($user->id)) 693 | { 694 | // found, log user in 695 | Auth::instance()->force_login($user); 696 | // redirect to the user account 697 | $this->request->redirect('user/profile'); 698 | return; 699 | } 700 | } 701 | // create new account 702 | if (! Auth::instance()->logged_in()) 703 | { 704 | // Instantiate a new user 705 | $user = ORM::factory('user'); 706 | // fill in values 707 | // generate long random password (maximum that passes validation is 42 characters) 708 | $password = $user->generate_password(42); 709 | $values = array( 710 | // get a unused username like firstname.surname or firstname.surname2 ... 711 | 'username' => $user->generate_username( 712 | str_replace(' ', '.', $provider->name()) 713 | ), 714 | 'password' => $password, 715 | 'password_confirm' => $password 716 | ); 717 | if (Valid::email($provider->email(), TRUE)) 718 | { 719 | $values['email'] = $provider->email(); 720 | } 721 | try 722 | { 723 | // If the post data validates using the rules setup in the user model 724 | $user->create_user($values, array( 725 | 'username', 726 | 'password', 727 | 'email' 728 | )); 729 | // Add the login role to the user (add a row to the db) 730 | $login_role = new Model_Role(array( 731 | 'name' => 'login' 732 | )); 733 | $user->add('roles', $login_role); 734 | // create user identity after we have the user id 735 | $user_identity = ORM::factory('user_identity'); 736 | $user_identity->user_id = $user->id; 737 | $user_identity->provider = $provider_name; 738 | $user_identity->identity = $provider->user_id(); 739 | $user_identity->save(); 740 | // sign the user in 741 | Auth::instance()->login($values['username'], $password); 742 | // redirect to the user account 743 | $this->request->redirect('user/profile'); 744 | } 745 | catch (ORM_Validation_Exception $e) 746 | { 747 | if ($provider_name == 'twitter') 748 | { 749 | Message::add('error', 'The Twitter API does not support retrieving your email address; you will have to enter it manually.'); 750 | } 751 | else 752 | { 753 | Message::add('error', 'We have successfully retrieved some of the data from your other account, but we were unable to get all the required fields. Please complete form below to register an account.'); 754 | } 755 | // in case the data for some reason fails, the user will still see something sensible: 756 | // the normal registration form. 757 | $view = View::factory('user/register'); 758 | $errors = $e->errors('register'); 759 | // Move external errors to main array, for post helper compatibility 760 | $errors = array_merge($errors, ( isset($errors['_external']) ? $errors['_external'] : array() )); 761 | $view->set('errors', $errors); 762 | // Pass on the old form values 763 | $values['password'] = $values['password_confirm'] = ''; 764 | $view->set('defaults', $values); 765 | if (Kohana::$config->load('useradmin')->captcha) 766 | { 767 | // FIXME: Is this the best place to include and use recaptcha? 768 | include Kohana::find_file('vendor', 'recaptcha/recaptchalib'); 769 | $recaptcha_config = Kohana::$config->load('recaptcha'); 770 | $recaptcha_error = null; 771 | $view->set('captcha_enabled', true); 772 | $view->set('recaptcha_html', recaptcha_get_html($recaptcha_config['publickey'], $recaptcha_error)); 773 | } 774 | $this->template->content = $view; 775 | } 776 | } 777 | else 778 | { 779 | Message::add('error', 'You are logged in, but the email received from the provider does not match the email associated with your account.'); 780 | $this->request->redirect('user/profile'); 781 | } 782 | } 783 | else 784 | { 785 | Message::add('error', 'Retrieving information from the provider failed. Please register below.'); 786 | $this->request->redirect('user/register'); 787 | } 788 | } 789 | 790 | /** 791 | * Media routing code. Allows lazy users to load images via Kohana. See also: init.php. 792 | * I recommend just serving the files via apache, e.g. copy the public directory to your webroot. 793 | */ 794 | public function action_media() 795 | { 796 | // prevent auto render 797 | $this->auto_render = FALSE; 798 | // Generate and check the ETag for this file 799 | // $this->request->check_cache(sha1($this->request->uri)); 800 | // Get the file path from the request 801 | $file = Request::current()->param('file'); 802 | $dir = Request::current()->param('dir'); 803 | // Find the file extension 804 | $ext = pathinfo($file, PATHINFO_EXTENSION); 805 | // Remove the extension from the filename 806 | $file = substr($file, 0, - ( strlen($ext) + 1 )); 807 | $file = Kohana::find_file('public', $dir . '/' . $file, $ext); 808 | if ($file) 809 | { 810 | // Send the file content as the response 811 | $this->response->body(file_get_contents($file)); 812 | } 813 | else 814 | { 815 | // Return a 404 status 816 | $this->response->status(404); 817 | } 818 | // Set the proper headers to allow caching 819 | $this->response->headers('Content-Type', File::mime_by_ext($ext)); 820 | $this->response->headers('Content-Length', (string) filesize($file)); 821 | $this->response->headers('Last-Modified', date('r', filemtime($file))); 822 | } 823 | } 824 | -------------------------------------------------------------------------------- /classes/useradmin/driver/iauth.php: -------------------------------------------------------------------------------- 1 | array( 16 | * label => string, 17 | * class => '' OR 'CSS class for element', 18 | * sortable => false OR true, 19 | * formatter => function for formatting cell data with this key 20 | * ) 21 | * ) 22 | * 23 | * @var array 24 | */ 25 | private $columns; 26 | 27 | /** 28 | * Configuration - array of general configuration for this datatable 29 | * 30 | * array( 31 | * paginator => KO3 Paginator class instance, 32 | * sortable => default value for sortable in columns, 33 | * default_sort => default column by which data is sorted, 34 | * default_dir => default direction in which data is sorted 35 | * ) 36 | * 37 | * @var array 38 | */ 39 | private $configuration; 40 | 41 | /** 42 | * Rows - array consisting of rows of data items. 43 | * @var array 44 | */ 45 | private $rows; 46 | 47 | function __construct($columns, $configuration = array()) 48 | { 49 | $this->columns = $columns; 50 | $this->configuration = $configuration; 51 | } 52 | 53 | /** 54 | * Add a row. 55 | * @param array $row 56 | * @param int $index (Optional) row index. 57 | */ 58 | function add($row, $index = null) 59 | { 60 | if (! is_numeric($index)) 61 | { 62 | $index = count($this->rows) - 1; 63 | } 64 | $this->rows[$index] = $row; 65 | } 66 | 67 | /** 68 | * Set the rows to the given value, replacing any old values. 69 | * @param array $rows 70 | */ 71 | function values($rows) 72 | { 73 | $this->rows = $rows; 74 | } 75 | 76 | /** 77 | * Get a single row, or all the rows. 78 | * @param int $index (Optional) row index. 79 | * @return array 80 | */ 81 | function get($index = null) 82 | { 83 | if (is_numeric($index)) 84 | { 85 | return $this->rows[$index]; 86 | } 87 | return $this->rows; 88 | } 89 | 90 | /** 91 | * Delete a row. 92 | * @param int $index Row index. 93 | */ 94 | function delete($index) 95 | { 96 | if (isset($this->rows[$index])) 97 | { 98 | unset($this->rows[$index]); 99 | } 100 | } 101 | 102 | /** 103 | * Render the datatable. 104 | * 105 | * Configuration: defaults to $_REQUEST 106 | * array( 107 | * sort => column key, 108 | * dir => 'ASC' or 'DESC', 109 | * page => int (page number) 110 | * ) 111 | * 112 | * @return string 113 | */ 114 | function render($params = null) 115 | { 116 | // create table 117 | $result = 'configuration['class']) ? ' class="' . $this->configuration['class'] . '"' : '' ) . '>'; 118 | if (! $params) 119 | { 120 | $params = $_REQUEST; 121 | } 122 | // get row sort info 123 | $sort = isset($params['sort']) ? $params['sort'] : false; 124 | if (! $sort && ! empty($this->configuration['default_sort'])) 125 | { 126 | $sort = $this->configuration['default_sort']; 127 | } 128 | $dir = isset($params['dir']) ? $params['dir'] : false; 129 | if (! $dir && ! empty($this->configuration['default_dir'])) 130 | { 131 | $dir = $this->configuration['default_dir']; 132 | } 133 | $page = isset($params['page']) ? $params['page'] : 1; 134 | // create heading 135 | $result .= ''; 136 | foreach ($this->columns as $name => $column) 137 | { 138 | if (! empty($column['sortable']) || ! empty($this->configuration['sortable']) && ! isset($column['sortable'])) 139 | { 140 | if (( $name == $sort && $dir == 'DESC' ) || $name != $sort) 141 | { 142 | $result .= '' 143 | . Html::anchor(URL::site(Request::current()->uri(), true) . URL::query(array( 144 | 'page' => $page, 145 | 'sort' => $name, 146 | 'dir' => null 147 | )), 148 | ( isset($column['label']) ? $column['label'] : $name ), 149 | ( $name == $sort ? array('class' => 'desc') : null )) 150 | . ''; 151 | } 152 | else 153 | { 154 | $result .= '' 155 | . Html::anchor(URL::site(Request::current()->uri(), true) . URL::query(array( 156 | 'page' => $page, 157 | 'sort' => $name, 158 | 'dir' => 'DESC' 159 | )), 160 | ( isset($column['label']) ? $column['label'] : $name ), 161 | ( $name == $sort ? array( 'class' => 'asc') : null )) 162 | . ''; 163 | } 164 | } 165 | else 166 | { 167 | $result .= '' 168 | . ( isset($column['label']) ? $column['label'] : $name ) 169 | . ''; 170 | } 171 | } 172 | $result .= ''; 173 | // print data 174 | $result .= ''; 175 | // array_merge renumbers the array, this is needed because unset (via deleteRow) will leave gaps in the indices. 176 | $this->rows = array_merge($this->rows); 177 | $end = count($this->rows); 178 | for ($i = 0; $i < $end; $i ++) 179 | { 180 | $result .= 'rows[$i]['_class'])) 182 | { 183 | $result .= ' class="' . $this->rows[$i]['_class'] . '"'; 184 | } 185 | else 186 | { 187 | if (( $i % 2 ) == 0) 188 | { 189 | $result .= ' class="odd"'; 190 | } 191 | else 192 | { 193 | $result .= ' class="even"'; 194 | } 195 | } 196 | $result .= '>'; 197 | foreach ($this->columns as $column => $settings) 198 | { 199 | $value = ''; 200 | // the value does not have to even exist for formatters to work 201 | // since they might just use some other columns in the data. 202 | if (isset($settings['formatter']) && is_callable($settings['formatter'])) 203 | { 204 | $value = call_user_func($settings['formatter'], 205 | $this->rows[$i]); 206 | } 207 | else 208 | if (isset($this->rows[$i][$column])) 209 | { 210 | $value = $this->rows[$i][$column]; 211 | } 212 | $result .= '' . $value . ''; 213 | } 214 | $result .= ''; 215 | } 216 | $result .= ''; 217 | $result .= ''; 218 | return $result; 219 | } 220 | } -------------------------------------------------------------------------------- /classes/useradmin/helper/format.php: -------------------------------------------------------------------------------- 1 | = 5.2.0 88 | $now = getdate(); 89 | if (( $date['year'] == $now['year'] ) && ( $date['month'] == $now['mon'] ) && ( $date['day'] == $now['mday'] )) 90 | { 91 | return 'today at ' . date('H:i:s', strtotime($value)); 92 | } 93 | else 94 | if (( $date['year'] == $now['year'] ) && ( $date['month'] == $now['month'] ) && ( $date['day'] == ( $now['day'] - 1 ) )) 95 | { 96 | return 'yesterday at ' . date('H:i:s', strtotime($value)); 97 | } 98 | else 99 | { 100 | return date('m/d/Y \a\t H:i:s', strtotime($value)); 101 | } 102 | } 103 | 104 | /** 105 | * Takes a MySQL format datetime yyyy-mm-dd and returns an European date dd/mm/yyyy 106 | * @param string $value 107 | * @return string 108 | */ 109 | public static function friendly_date($value) 110 | { 111 | if (( $value == '0000-00-00 00:00:00' ) || ( $value == '0000-00-00' )) 112 | { 113 | return __('never'); 114 | } 115 | return date('m/d/Y', strtotime($value)); 116 | } 117 | } -------------------------------------------------------------------------------- /classes/useradmin/message.php: -------------------------------------------------------------------------------- 1 | get('messages'); 9 | // initialize if necessary 10 | if (! is_array($messages)) 11 | { 12 | $messages = array(); 13 | } 14 | // append to messages 15 | $messages[$type][] = $message; 16 | // set messages 17 | Session::instance()->set('messages', $messages); 18 | } 19 | 20 | public static function count() 21 | { 22 | return count(Session::instance()->get('messages')); 23 | } 24 | 25 | public static function output() 26 | { 27 | $str = ''; 28 | $messages = Session::instance()->get('messages'); 29 | Session::instance()->delete('messages'); 30 | if (! empty($messages)) 31 | { 32 | foreach ($messages as $type => $messages) 33 | { 34 | foreach ($messages as $message) 35 | { 36 | $str .= '
    ' . $message . '
    '; 37 | } 38 | } 39 | } 40 | return $str; 41 | } 42 | } -------------------------------------------------------------------------------- /classes/useradmin/model/user.php: -------------------------------------------------------------------------------- 1 | array('through' => 'roles_users'), 17 | 'user_tokens' => array(), 18 | // for facebook / twitter / google / yahoo identities 19 | 'user_identity' => array(), 20 | ); 21 | 22 | protected $_has_one= array( 23 | ); 24 | 25 | protected $_created_column = array('column' => 'created', 'format' => 'Y-m-d H:i:s'); 26 | 27 | protected $_updated_column = array('column' => 'modified', 'format' => 'Y-m-d H:i:s'); 28 | 29 | /** 30 | * Rules for the user model. Because the password is _always_ a hash 31 | * when it's set,you need to run an additional not_empty rule in your controller 32 | * to make sure you didn't hash an empty string. The password rules 33 | * should be enforced outside the model or with a model helper method. 34 | * 35 | * @return array Rules 36 | * @see Model_Auth_User::rules 37 | */ 38 | public function rules() 39 | { 40 | $parent = parent::rules(); 41 | // fixes the min_length username value 42 | $parent['username'][1] = array('min_length', array(':value', 1)); 43 | return $parent; 44 | } 45 | 46 | // TODO overload filters() and add username/created_on/updated_on coluns filters 47 | 48 | /** 49 | * Password validation for plain passwords. 50 | * 51 | * @param array $values 52 | * @return Validation 53 | * @see Model_Auth_User::get_password_validation 54 | */ 55 | public static function get_password_validation($values) 56 | { 57 | return Validation::factory($values) 58 | ->rule('password', 'min_length', array(':value', 6)) 59 | ->rule('password_confirm', 'matches', array(':validation', ':field', 'password')); 60 | } 61 | 62 | /** 63 | * Generates a password of given length using mt_rand. 64 | * 65 | * @param int $length 66 | * @return string 67 | */ 68 | function generate_password($length = 8) 69 | { 70 | // start with a blank password 71 | $password = ""; 72 | // define possible characters (does not include l, number relatively likely) 73 | $possible = "123456789abcdefghjkmnpqrstuvwxyz123456789"; 74 | // add random characters to $password until $length is reached 75 | for ($i = 0; $i < $length; $i++) 76 | { 77 | // pick a random character from the possible ones 78 | $char = substr($possible, mt_rand(0, strlen($possible)-1), 1); 79 | $password .= $char; 80 | } 81 | return $password; 82 | } 83 | 84 | /** 85 | * Transcribe name to ASCII 86 | * 87 | * @param string $string 88 | * @return string 89 | */ 90 | function transcribe($string) 91 | { 92 | $string = strtr($string, 93 | "\xA1\xAA\xBA\xBF\xC0\xC1\xC2\xC3\xC5\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD8\xD9\xDA\xDB\xDD\xE0\xE1\xE2\xE3\xE5\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF8\xF9\xFA\xFB\xFD\xFF\xC4\xD6\xE4\xF6", 94 | "_ao_AAAAACEEEEIIIIDNOOOOOUUUYaaaaaceeeeiiiidnooooouuuyyAOao" 95 | ); 96 | $string = strtr($string, array("\xC6"=>"AE", "\xDC"=>"Ue", "\xDE"=>"TH", "\xDF"=>"ss", "\xE6"=>"ae", "\xFC"=>"ue", "\xFE"=>"th")); 97 | $string = preg_replace("/([^a-z0-9\\.]+)/", "", strtolower($string)); 98 | return($string); 99 | } 100 | 101 | /** 102 | * Given a string, this function will try to find an unused username by appending a number. 103 | * Ex. username2, username3, username4 ... 104 | * 105 | * @param string $base 106 | */ 107 | function generate_username($base = '') 108 | { 109 | $base = $this->transcribe($base); 110 | $username = $base; 111 | $i = 2; 112 | // check for existent username 113 | while( $this->username_exist($username) ) 114 | { 115 | $username = $base.$i; 116 | $i++; 117 | } 118 | return $username; 119 | } 120 | 121 | /** 122 | * Check whether a username exists. 123 | * @param string $username 124 | * @return boolean 125 | */ 126 | public function username_exist($username) 127 | { 128 | return ( (bool) $this->unique_key_exists( $username, "username") ) ; 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /classes/useradmin/model/user/identity.php: -------------------------------------------------------------------------------- 1 | array() 7 | ); 8 | 9 | /** 10 | * Rules for the user identity. 11 | * @return array Rules 12 | */ 13 | public function rules () 14 | { 15 | return array( 16 | 'user_id' => array( 17 | array('not_empty'), 18 | array('numeric') 19 | ), 20 | 'provider' => array( 21 | array('not_empty'), 22 | array('max_length', array(':value', 255) ) 23 | ), 24 | 'identity' => array( 25 | array('not_empty'), 26 | array('max_length', array(':value', 255) ), 27 | array(array($this, 'unique_identity'), array(':validation', ':field') ) 28 | ) 29 | ); 30 | } 31 | 32 | /** 33 | * Triggers error if identity exists. 34 | * Validation callback. 35 | * 36 | * @param Validation Validation object 37 | * @param string field name 38 | * @return void 39 | */ 40 | public function unique_identity (Validation $validation, $field) 41 | { 42 | $identity_exists = (bool) DB::select(array('COUNT("*")', 'total_count')) 43 | ->from($this->_table_name) 44 | ->where('identity', '=', $validation['identity']) 45 | ->and_where('provider', '=', $validation['provider']) 46 | ->execute($this->_db) 47 | ->get('total_count'); 48 | if ($identity_exists) 49 | { 50 | $validation->error($field, 'identity_available', array($validation[$field])); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /classes/useradmin/provider.php: -------------------------------------------------------------------------------- 1 | load('useradmin.providers'); 14 | if (! empty($provider_name) && isset($providers[$provider_name]) && $providers[$provider_name]) 15 | { 16 | switch ($provider_name) 17 | { 18 | case 'facebook': 19 | $provider = new Provider_Facebook(); 20 | break; 21 | case 'twitter': 22 | $provider = new Provider_Twitter(); 23 | break; 24 | case 'google': 25 | $provider = new Provider_OpenID('google'); 26 | break; 27 | case 'yahoo': 28 | $provider = new Provider_OpenID('yahoo'); 29 | break; 30 | } 31 | } 32 | return $provider; 33 | } 34 | 35 | /** 36 | * Get the URL to redirect to. 37 | * @return string 38 | */ 39 | abstract public function redirect_url($return_url); 40 | 41 | /** 42 | * Verify the login result and do whatever is needed to access the user data from this provider. 43 | * @return bool 44 | */ 45 | abstract public function verify(); 46 | 47 | /** 48 | * Attempt to get the provider user ID. 49 | * @return mixed 50 | */ 51 | abstract public function user_id(); 52 | 53 | /** 54 | * Attempt to get the email from the provider (e.g. for finding an existing account to associate with). 55 | * @return string 56 | */ 57 | abstract public function email(); 58 | 59 | /** 60 | * Get the full name (firstname surname) from the provider. 61 | * @return string 62 | */ 63 | abstract public function name(); 64 | } -------------------------------------------------------------------------------- /classes/useradmin/provider/facebook.php: -------------------------------------------------------------------------------- 1 | facebook = new Facebook(array( 20 | 'appId' => Kohana::$config->load('facebook')->app_id, 21 | 'secret' => Kohana::$config->load('facebook')->secret, 22 | 'cookie' => true // enable optional cookie support 23 | )); 24 | } 25 | 26 | /** 27 | * Get the URL to redirect to. 28 | * @return string 29 | */ 30 | public function redirect_url($return_url) 31 | { 32 | return $this->facebook->getLoginUrl(array( 33 | 'next' => URL::site($return_url, true), 34 | 'cancel_url' => URL::site($return_url, true), 35 | 'req_perms' => 'email' 36 | )); 37 | } 38 | 39 | /** 40 | * Verify the login result and do whatever is needed to access the user data from this provider. 41 | * @return bool 42 | */ 43 | public function verify() 44 | { 45 | if ($this->facebook->getSession()) 46 | { 47 | try 48 | { 49 | $this->uid = $this->facebook->getUser(); 50 | // read user info as array from Graph API 51 | $this->me = $this->facebook->api('/me'); 52 | } 53 | catch (FacebookApiException $e) 54 | { 55 | return false; 56 | } 57 | return true; 58 | } 59 | return false; 60 | } 61 | 62 | /** 63 | * Attempt to get the provider user ID. 64 | * @return mixed 65 | */ 66 | public function user_id() 67 | { 68 | return $this->uid; 69 | } 70 | 71 | /** 72 | * Attempt to get the email from the provider (e.g. for finding an existing account to associate with). 73 | * @return string 74 | */ 75 | public function email() 76 | { 77 | if (isset($this->me['email'])) 78 | { 79 | return $this->me['email']; 80 | } 81 | return ''; 82 | } 83 | 84 | /** 85 | * Get the full name (firstname surname) from the provider. 86 | * @return string 87 | */ 88 | public function name() 89 | { 90 | if (isset($this->me['first_name'])) 91 | { 92 | return $this->me['first_name'] . ' ' . $this->me['last_name']; 93 | } 94 | return ''; 95 | } 96 | } -------------------------------------------------------------------------------- /classes/useradmin/provider/oauth.php: -------------------------------------------------------------------------------- 1 | provider_name = $provider; 28 | // Load the configuration for this provider 29 | $config = Kohana::$config->load('oauth.' . $this->provider_name); 30 | // Create an consumer from the config 31 | $this->consumer = OAuth_Consumer::factory($config); 32 | // Load the provider 33 | $this->provider = OAuth_Provider::factory($this->provider_name); 34 | } 35 | 36 | /** 37 | * Get the URL to redirect to. 38 | * @return string 39 | */ 40 | public function redirect_url($return_url) 41 | { 42 | // Add the callback URL to the consumer 43 | $this->consumer->callback(URL::site($return_url, true)); 44 | // Get a request token for the consumer 45 | $request_token = $this->provider->request_token($this->consumer); 46 | Session::instance()->set('oauth_token', $request_token->token); 47 | Session::instance()->set('oauth_token_secret', $request_token->secret); 48 | // Redirect to the twitter login page 49 | return $this->provider->authorize_url($request_token); 50 | } 51 | } -------------------------------------------------------------------------------- /classes/useradmin/provider/openid.php: -------------------------------------------------------------------------------- 1 | array('url' => 'https://www.google.com/accounts/o8/id'), 10 | 'yahoo' => array('url' => 'https://me.yahoo.com/') 11 | ); 12 | 13 | private $provider = null; 14 | 15 | private $provider_name = ''; 16 | 17 | private $uid = null; 18 | 19 | private $data = null; 20 | 21 | public function __construct($provider_name) 22 | { 23 | include_once Kohana::find_file('vendor', 'lightopenid/openid'); 24 | $this->provider = new LightOpenID(); 25 | $this->provider_name = $provider_name; 26 | } 27 | 28 | /** 29 | * Get the URL to redirect to. 30 | * @return string 31 | */ 32 | public function redirect_url($return_url) 33 | { 34 | $this->provider->identity = Provider_OpenID::$config[$this->provider_name]['url']; 35 | $this->provider->returnUrl = URL::site($return_url, true); 36 | $this->provider->required = array( 37 | 'namePerson', 38 | 'namePerson/first', 39 | 'namePerson/last', 40 | 'contact/email' 41 | ); 42 | return $this->provider->authUrl(); 43 | } 44 | 45 | /** 46 | * Verify the login result and do whatever is needed to access the user data from this provider. 47 | * @return bool 48 | */ 49 | public function verify() 50 | { 51 | if ($this->provider->validate()) 52 | { 53 | $this->uid = $this->provider->identity; 54 | $this->data = $this->provider->getAttributes(); 55 | return true; 56 | } 57 | return false; 58 | } 59 | 60 | /** 61 | * Attempt to get the provider user ID. 62 | * @return mixed 63 | */ 64 | public function user_id() 65 | { 66 | return $this->uid; 67 | } 68 | 69 | /** 70 | * Attempt to get the email from the provider (e.g. for finding an existing account to associate with). 71 | * @return string 72 | */ 73 | public function email() 74 | { 75 | if (isset($this->data['contact/email'])) 76 | { 77 | return $this->data['contact/email']; 78 | } 79 | return ''; 80 | } 81 | 82 | /** 83 | * Get the full name (firstname surname) from the provider. 84 | * @return string 85 | */ 86 | public function name() 87 | { 88 | // YAHOO supports this ax 89 | if (isset($this->data['namePerson'])) 90 | { 91 | return $this->data['namePerson']; 92 | } 93 | // GOOGLE uses these... 94 | if (isset($this->data['namePerson/first']) && isset($this->data['namePerson/last'])) 95 | { 96 | return $this->data['namePerson/first'] . ' ' . $this->data['namePerson/last']; 97 | } 98 | return ''; 99 | } 100 | } -------------------------------------------------------------------------------- /classes/useradmin/provider/twitter.php: -------------------------------------------------------------------------------- 1 | Session::instance()->get('oauth_token'), 27 | 'secret' => Session::instance()->get('oauth_token_secret') 28 | )); 29 | // Store the verifier in the token 30 | $request_token->verifier($_REQUEST['oauth_verifier']); 31 | // Exchange the request token for an access token 32 | $access_token = $this->provider->access_token($this->consumer, $request_token); 33 | if ($access_token and $access_token->name === 'access') 34 | { 35 | // @link http://dev.twitter.com/doc/get/account/verify_credentials 36 | $request = OAuth_Request::factory('resource', 'GET', 'http://api.twitter.com/1/account/verify_credentials.json', array( 37 | 'oauth_consumer_key' => $this->consumer->key, 38 | 'oauth_token' => $access_token->token 39 | )); 40 | // Sign the request using only the consumer, no token is available yet 41 | $request->sign(new OAuth_Signature_HMAC_SHA1(), $this->consumer, $access_token); 42 | // decode and store data 43 | $data = json_decode($request->execute(), true); 44 | $this->uid = $data['id']; 45 | $this->data = $data; 46 | return true; 47 | } 48 | else 49 | { 50 | return false; 51 | } 52 | } 53 | 54 | /** 55 | * Attempt to get the provider user ID. 56 | * @return mixed 57 | */ 58 | public function user_id() 59 | { 60 | return $this->uid; 61 | } 62 | 63 | /** 64 | * Attempt to get the email from the provider (e.g. for finding an existing account to associate with). 65 | * @return string 66 | */ 67 | public function email() 68 | { 69 | if (isset($this->data['email'])) 70 | { 71 | return $this->data['email']; 72 | } 73 | return ''; 74 | } 75 | 76 | /** 77 | * Get the full name (firstname surname) from the provider. 78 | * @return string 79 | */ 80 | public function name() 81 | { 82 | if (isset($this->data['name'])) 83 | { 84 | return $this->data['name']; 85 | } 86 | else 87 | if (isset($this->data['screen_name'])) 88 | { 89 | return $this->data['screen_name']; 90 | } 91 | return ''; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /config/facebook.php: -------------------------------------------------------------------------------- 1 | '', 5 | 'api_key' => '', 6 | 'secret' => '', 7 | ); 8 | -------------------------------------------------------------------------------- /config/oauth.php: -------------------------------------------------------------------------------- 1 | array( 13 | 'key' => 'your consumer key', 14 | 'secret' => 'your consumer secret' 15 | ), 16 | ); -------------------------------------------------------------------------------- /config/recaptcha.php: -------------------------------------------------------------------------------- 1 | '', 5 | 'privatekey' => '' 6 | ); 7 | -------------------------------------------------------------------------------- /config/useradmin.php: -------------------------------------------------------------------------------- 1 | array( 10 | /** 11 | * Define the maximum failed attempts to login 12 | * set 0 to disable the login jail 13 | */ 14 | 'max_failed_logins' => 5, 15 | /** 16 | * Define the time that user who archive the max_failed_logins will need to 17 | * wait before his next attempt 18 | */ 19 | 'login_jail_time' => "5 minutes", 20 | ), 21 | /** 22 | * 3rd party providers supported/allowed. 23 | */ 24 | 'providers' => array( 25 | /** 26 | * Toggle Facebook support: if set, then users can log in using Facebook. 27 | * 28 | * Setup: 29 | * - You need the extra table from schema.sql for storing 3rd party identifiers 30 | * - You must register your app with FB and add the information in /config/facebook.php 31 | * - You must have the Facebook SDK at /vendors/facebook/src/facebook.php (bundled in the default repo) 32 | * 33 | */ 34 | 'facebook' => true, 35 | /** 36 | * Toggle Twitter support: if set, users can log in using Twitter 37 | * 38 | * Setup: 39 | * - You need the extra table from schema.sql for storing 3rd party identifiers 40 | * - You must register your app with Twitter and add the information in /config/oauth.php (Kohana-Oauth's config) 41 | * - You must enable the Kohana Core oauth module 42 | */ 43 | 'twitter' => true, 44 | /** 45 | * Toggle Google support: if set, users can log in using their Google account. 46 | * 47 | * Setup: 48 | * - You need the extra table from schema.sql for storing 3rd party identifiers 49 | * - You must have LightOpenID in /vendors/lightopenid/openid.php (bundled in the repo) 50 | */ 51 | 'google' => true, 52 | /** 53 | * Toggle Yahoo support: if set, users can log in using their Yahoo account. 54 | * 55 | * Setup: 56 | * - You need the extra table from schema.sql for storing 3rd party identifiers 57 | * - You must have LightOpenID in /vendors/lightopenid/openid.php (bundled in the repo) 58 | */ 59 | 'yahoo' => true, 60 | ), 61 | /** 62 | * Toggle email support: if set, then users (except admins) can reset user accounts via email. 63 | * They will be sent an email with a reset token, which they enter, then their password will be reset to a new random password. 64 | * 65 | * Setup: 66 | * - You must have the Kohana-email module enabled (bundled in default repo) 67 | */ 68 | 'email' => true, 69 | /* change this to the email address you want the password reset emails to come from. */ 70 | 'email_address' => 'no-response@example.com', 71 | /** 72 | * Toggle reCaptcha support: if set, then during registration the user is shown 73 | * a reCaptcha which they must answer correctly (unless they are using one of the 3rd party accounts). 74 | * 75 | * Setup 76 | * - You must have the reCaptcha library (e.g. http://recaptcha.net) in your vendors directory. (bundled in the default repo) 77 | * - You must set the private and public key in /config/recaptcha.php from https://www.google.com/recaptcha/admin/create 78 | */ 79 | 'captcha' => false, 80 | ); 81 | -------------------------------------------------------------------------------- /database/useradmin.sql: -------------------------------------------------------------------------------- 1 | -- MySQL dump 10.13 Distrib 5.5.8, for Linux (x86_64) 2 | -- 3 | -- Host: localhost Database: useradmin 4 | -- ------------------------------------------------------ 5 | -- Server version 5.5.8 6 | 7 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 8 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 9 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 10 | /*!40101 SET NAMES utf8 */; 11 | /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; 12 | /*!40103 SET TIME_ZONE='+00:00' */; 13 | /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; 14 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 15 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 16 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 17 | 18 | -- 19 | -- Table structure for table `roles` 20 | -- 21 | 22 | DROP TABLE IF EXISTS `roles`; 23 | /*!40101 SET @saved_cs_client = @@character_set_client */; 24 | /*!40101 SET character_set_client = utf8 */; 25 | CREATE TABLE `roles` ( 26 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 27 | `name` varchar(32) NOT NULL, 28 | `description` varchar(255) NOT NULL, 29 | PRIMARY KEY (`id`), 30 | UNIQUE KEY `uniq_name` (`name`) 31 | ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; 32 | /*!40101 SET character_set_client = @saved_cs_client */; 33 | 34 | -- 35 | -- Dumping data for table `roles` 36 | -- 37 | 38 | INSERT INTO `roles` (`id`, `name`, `description`) VALUES 39 | (1, 'login', 'Login privileges, granted after account confirmation'), 40 | (2, 'admin', 'Administrative user, has access to everything.'); 41 | 42 | -- 43 | -- Dumping data for table `users` 44 | -- 45 | 46 | -- -------------------------------------------------------- 47 | 48 | -- 49 | -- Table structure for table `roles_users` 50 | -- 51 | 52 | DROP TABLE IF EXISTS `roles_users`; 53 | /*!40101 SET @saved_cs_client = @@character_set_client */; 54 | /*!40101 SET character_set_client = utf8 */; 55 | CREATE TABLE `roles_users` ( 56 | `user_id` int(10) unsigned NOT NULL, 57 | `role_id` int(10) unsigned NOT NULL, 58 | PRIMARY KEY (`user_id`,`role_id`), 59 | KEY `fk_role_id` (`role_id`), 60 | CONSTRAINT `roles_users_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE, 61 | CONSTRAINT `roles_users_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE 62 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 63 | /*!40101 SET character_set_client = @saved_cs_client */; 64 | 65 | -- -------------------------------------------------------- 66 | 67 | -- 68 | -- Table structure for table `user_identities` 69 | -- 70 | 71 | DROP TABLE IF EXISTS `user_identities`; 72 | /*!40101 SET @saved_cs_client = @@character_set_client */; 73 | /*!40101 SET character_set_client = utf8 */; 74 | CREATE TABLE `user_identities` ( 75 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 76 | `user_id` int(11) unsigned NOT NULL, 77 | `provider` varchar(255) NOT NULL, 78 | `identity` varchar(255) NOT NULL, 79 | PRIMARY KEY (`id`), 80 | UNIQUE KEY `uniq_indentity` (`provider`,`identity`), 81 | KEY `fk_user_id` (`user_id`), 82 | CONSTRAINT `user_identities_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE 83 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1; 84 | /*!40101 SET character_set_client = @saved_cs_client */; 85 | 86 | -- -------------------------------------------------------- 87 | 88 | -- 89 | -- Table structure for table `user_tokens` 90 | -- 91 | 92 | DROP TABLE IF EXISTS `user_tokens`; 93 | /*!40101 SET @saved_cs_client = @@character_set_client */; 94 | /*!40101 SET character_set_client = utf8 */; 95 | CREATE TABLE `user_tokens` ( 96 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 97 | `user_id` int(11) unsigned NOT NULL, 98 | `user_agent` varchar(40) NOT NULL, 99 | <<<<<<< HEAD 100 | `token` varchar(40) NOT NULL, /* sha1 (which is Cookie:salt hashing method) hash - is 40 bytes long*/ 101 | ======= 102 | `token` varchar(40) NOT NULL, 103 | >>>>>>> brennan87/develop 104 | `created` int(10) unsigned NOT NULL, 105 | `expires` int(10) unsigned NOT NULL, 106 | PRIMARY KEY (`id`), 107 | UNIQUE KEY `uniq_token` (`token`), 108 | KEY `fk_user_id` (`user_id`), 109 | CONSTRAINT `user_tokens_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE 110 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 111 | /*!40101 SET character_set_client = @saved_cs_client */; 112 | 113 | -- -------------------------------------------------------- 114 | 115 | -- 116 | -- Table structure for table `users` 117 | -- 118 | 119 | DROP TABLE IF EXISTS `users`; 120 | /*!40101 SET @saved_cs_client = @@character_set_client */; 121 | /*!40101 SET character_set_client = utf8 */; 122 | CREATE TABLE `users` ( 123 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 124 | `email` varchar(127) NOT NULL, 125 | `username` varchar(32) NOT NULL DEFAULT '', 126 | `password` char(64) NOT NULL, 127 | `logins` int(10) unsigned NOT NULL DEFAULT '0', 128 | `last_login` int(10) unsigned DEFAULT NULL, 129 | `reset_token` char(64) NOT NULL DEFAULT '', 130 | `status` varchar(20) NOT NULL DEFAULT '', 131 | `last_failed_login` datetime NOT NULL, 132 | `failed_login_count` int(11) NOT NULL DEFAULT '0', 133 | `created` datetime NOT NULL, 134 | `modified` datetime NOT NULL, 135 | PRIMARY KEY (`id`), 136 | UNIQUE KEY `uniq_username` (`username`), 137 | UNIQUE KEY `uniq_email` (`email`) 138 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 139 | /*!40101 SET character_set_client = @saved_cs_client */; 140 | /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; 141 | 142 | /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; 143 | /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; 144 | /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; 145 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 146 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 147 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 148 | /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; 149 | 150 | -- Dump completed on 2011-02-23 21:25:17 151 | -------------------------------------------------------------------------------- /docs/Useradmin_Auth.dia: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixu/useradmin/458bedd41c73743395d9350c9cf766b862c55e81/docs/Useradmin_Auth.dia -------------------------------------------------------------------------------- /docs/Useradmin_Auth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixu/useradmin/458bedd41c73743395d9350c9cf766b862c55e81/docs/Useradmin_Auth.png -------------------------------------------------------------------------------- /init.php: -------------------------------------------------------------------------------- 1 | )', array('provider' => '.+')) 4 | ->defaults(array( 5 | 'controller' => 'user', 6 | 'action' => 'provider', 7 | 'provider' => NULL, 8 | )); 9 | 10 | Route::set('user/provider_return', 'user/provider_return(/)', array('provider' => '.+')) 11 | ->defaults(array( 12 | 'controller' => 'user', 13 | 'action' => 'provider_return', 14 | 'provider' => NULL, 15 | )); 16 | 17 | // Static file serving (CSS, JS, images) 18 | Route::set('css', '(/)', array('file' => '.+', 'dir' => '(css|img)')) 19 | ->defaults(array( 20 | 'controller' => 'user', 21 | 'action' => 'media', 22 | 'file' => NULL, 23 | 'dir' => NULL, 24 | )); 25 | 26 | -------------------------------------------------------------------------------- /messages/login.php: -------------------------------------------------------------------------------- 1 | array( 6 | 'not_empty' => 'Username must not be empty.', 7 | 'invalid' => 'Password or username is incorrect.', 8 | ), 9 | 'password' => array( 10 | 'not_empty' => 'Password must not be empty.', 11 | 'invalid' => 'Password or username is incorrect.', 12 | ), 13 | ); 14 | 15 | -------------------------------------------------------------------------------- /messages/register/user.php: -------------------------------------------------------------------------------- 1 | array( 6 | 'username_available' => 'This username is already registered, please choose another one.', 7 | 'username_not_unique' => 'This username is already in use.', 8 | ), 9 | 'email' => array( 10 | 'email' => 'This is not a valid email.', // Workaround for Bug Report #3750 11 | 'email_available' => 'This email address is already in use.', 12 | 'email_not_unique' => 'This email address is already in use.', 13 | ), 14 | 'email_confirm' => array( 15 | 'email' => 'This is not a valid email.', // Workaround for Bug Report #3750 16 | 'email_available' => 'This email address is already in use.', 17 | 'email_not_unique' => 'This email address is already in use.', 18 | ), 19 | 'password' => array( 20 | 'matches' => 'The password and password confirmation are different.', 21 | ), 22 | 'password_confirm' => array( 23 | 'matches' => 'The password and password confirmation are different.', 24 | ), 25 | ); 26 | 27 | -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | /** Css reset: except table{border-collapse:collapse;border-spacing:0;} **/ 2 | html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,button,textarea,p,blockquote,th,td{margin:0;padding:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var,optgroup{font-style:inherit;font-weight:inherit;}del,ins{text-decoration:none;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym{border:0;font-variant:normal;}sup{vertical-align:baseline;}sub{vertical-align:baseline;}legend{color:#000;}input,button,textarea,select,optgroup,option{font-family:inherit;font-size:inherit;font-style:inherit;font-weight:inherit;}input,button,textarea,select{font-size:100%;} 3 | 4 | /* Global styles: body, paragraphs, lists etc */ 5 | 6 | html { 7 | background-color: #E5E5E5; 8 | } 9 | body { 10 | font-family: Arial,Helvetica,Sans-serif; 11 | margin: 0px; 12 | } 13 | 14 | /* Page */ 15 | 16 | /* Header */ 17 | 18 | #header-logo { 19 | border: 0pt none; 20 | padding: 0px; 21 | margin: 0px; 22 | background-color: transparent; 23 | } 24 | 25 | /* Content */ 26 | #content { 27 | padding-left: 20px; 28 | } 29 | 30 | 31 | p { 32 | margin-bottom:0.7em; 33 | margin-top:0.3em; 34 | } 35 | 36 | /* Menus */ 37 | 38 | /** Nice looking "new" menu style. **/ 39 | ul.menu { 40 | background:none repeat scroll 0 0 #356AA0; 41 | border-bottom:2px solid #204061; 42 | border-top:2px solid #356AA0; 43 | 44 | font-family:Arial,sans-serif; 45 | font-size:15px; 46 | font-weight:bold; 47 | height:40px; 48 | list-style-type:none; 49 | padding:0; 50 | } 51 | 52 | ul.menu li{ 53 | float:left; 54 | margin:0; 55 | } 56 | 57 | ul.menu li a { 58 | color:#FFFFFF; 59 | display:block; 60 | line-height:40px; 61 | padding:0 20px; 62 | text-decoration:none; 63 | } 64 | 65 | ul.menu li.active a, ul.menu li a:hover { 66 | background:none repeat scroll 0 0 #3D7BBB; 67 | border-bottom:2px solid #356AA0; 68 | } 69 | 70 | /** Nice looking "new" submenu style. **/ 71 | ul.submenu { 72 | background:none repeat scroll 0 0 #333333; 73 | border-bottom:2px solid #000000; 74 | border-top:2px solid #333333; 75 | 76 | font-family:Arial,sans-serif; 77 | font-size:13px; 78 | height:25px; 79 | list-style-type:none; 80 | padding:0; 81 | } 82 | 83 | ul.submenu li{ 84 | float:left; 85 | margin:0; 86 | } 87 | 88 | ul.submenu li a { 89 | color:#FFFFFF; 90 | display:block; 91 | line-height:25px; 92 | padding:0 10px; 93 | text-decoration:none; 94 | } 95 | 96 | ul.submenu li a:hover { 97 | background:none repeat scroll 0 0 #555555; 98 | border-bottom:2px solid #444444; 99 | } 100 | 101 | 102 | /* Forms */ 103 | 104 | /* Simple way to create labels-on-top */ 105 | label { 106 | display: block; 107 | } 108 | /** Error color **/ 109 | input.error { 110 | color:#CC0000; 111 | border:solid 1px #de888a; background:#ffdddd; 112 | } 113 | /** When IE6 dies, replace with element-specifics. This is supposed to apply to only text, password and file. **/ 114 | input.text, input.password, input.file { 115 | border:1px solid #DEDEDE; 116 | color:#525151; 117 | font-size:20px; 118 | height:30px; 119 | padding:2px 0 0 5px; 120 | width:420px; 121 | margin-bottom:10px; 122 | } 123 | select { 124 | margin-bottom:10px; 125 | } 126 | /** Again, without IE6 these would be element type selectors. We need a small margin. **/ 127 | input.radio, input.checkbox { 128 | margin:0 0.4em 0 0; 129 | } 130 | textarea { 131 | border:1px solid #DEDEDE; 132 | color:#525151; 133 | font-size:12pt; 134 | padding:2px 0 0 5px; 135 | width:420px; 136 | margin-bottom: 10px; 137 | } 138 | 139 | input.submit { 140 | font-size: 18px; 141 | padding-left: 10px; 142 | padding-right: 10px; 143 | } 144 | 145 | /** Error and info spans **/ 146 | span.error { 147 | padding: 5px; 148 | color:#CC0000; 149 | border:solid 2px #de888a; background:#ffdddd; 150 | margin-top: 0px; margin-bottom: 0px; 151 | margin-left: 4px; 152 | display: inline-block; 153 | vertical-align: top; 154 | font-size: smaller; 155 | max-width: 465px; 156 | /* IE7: haslayout to get inline-block */ 157 | zoom: 1; 158 | *display: inline; 159 | } 160 | 161 | span.info { 162 | padding: 5px; 163 | color:#5C5C5C; 164 | border:solid 2px #CBDFE7; background:#DEF1F8; 165 | margin-top: 0px; margin-bottom: 0px; 166 | margin-left: 4px; 167 | display: inline-block; 168 | vertical-align: top; 169 | font-size: smaller; 170 | max-width: 465px; 171 | /* IE7: haslayout to get inline-block */ 172 | zoom: 1; 173 | *display: inline; 174 | } 175 | 176 | span.block { 177 | display: block; 178 | margin-left: 0px; 179 | margin-bottom: 4px; 180 | } 181 | 182 | /* Messages */ 183 | 184 | div.info, div.success, div.error { 185 | -moz-border-radius: 6px; 186 | -webkit-border-radius: 6px; 187 | border-radius: 6px; 188 | border-top-left-radius: 6px 6px; 189 | border-top-right-radius: 6px 6px; 190 | border-bottom-right-radius: 6px 6px; 191 | border-bottom-left-radius: 6px 6px; 192 | } 193 | 194 | div.info { 195 | padding: 6px; 196 | color:#5C5C5C; 197 | border:solid 1px #CBDFE7; background:#DEF1F8; margin-top: 4px; margin-bottom: 4px; 198 | margin-right: 6px; 199 | } 200 | 201 | div.success { 202 | padding: 6px; 203 | color:#008000; 204 | border:solid 1px #349534; background:#C9FFCA; margin-top: 4px; margin-bottom: 4px; 205 | } 206 | 207 | div.error { 208 | padding: 6px; 209 | color: black; 210 | border:solid 1px #FBB; background: #FDD; margin-top: 4px; margin-bottom: 4px; 211 | } 212 | 213 | /* Content tables */ 214 | table.content, table.content th, table.content td { 215 | border-collapse: collapse; 216 | font-family: "Trebuchet MS", Arial, sans-serif; 217 | color: #555; 218 | border-bottom: 1px solid #eeeeee; 219 | } 220 | table.content td.caption { 221 | text-align: right; 222 | } 223 | 224 | /* Can use either tr.heading and td, or thead and th to create a heading..*/ 225 | table.content tr.heading td, table.content thead th { 226 | border-top:1px solid #CCCCCC; 227 | border-bottom:1px solid #CCCCCC; 228 | padding-top: 6px; 229 | color: #000000; 230 | } 231 | 232 | table.content td, table.content th { 233 | padding-bottom:5px; 234 | padding-top:5px; 235 | padding-left:4px; 236 | padding-right:4px; 237 | } 238 | 239 | /* Table cells containing a caption have this class */ 240 | table.content td.caption { 241 | text-align:right; 242 | } 243 | 244 | /* Row colors */ 245 | 246 | table.content tr { background: #FCFDFE; } 247 | 248 | /* Can use TR.odd or TD.odd for odd rows/columns */ 249 | table.content tr.odd, table.content td.odd { background: #eeeeee; } 250 | 251 | 252 | #content { 253 | padding: 20px 20px; 254 | } 255 | 256 | #header { 257 | background-color: black; 258 | padding-left: 20px; 259 | } 260 | 261 | ul.menu { 262 | background-color: #f4f4f4; 263 | background-image: url("/img/menubar-background.png"); 264 | background-repeat: repeat-x; 265 | border: 0px none; 266 | } 267 | ul.menu li a { 268 | color: black; 269 | font-weight: normal; 270 | } 271 | 272 | ul.menu li.active a, ul.menu li a:hover { 273 | background:none repeat scroll 0 0 #EAEAEA; 274 | border-bottom: 0px none; 275 | } 276 | 277 | 278 | #header h1 { 279 | padding: 15px 0; 280 | font-size: 32px; 281 | font-style: normal; 282 | font-weight: bold; 283 | text-transform: none; 284 | letter-spacing: -1px; 285 | line-height: 1.2em; 286 | color: white; 287 | text-shadow: black 1px 1px 2px; 288 | } 289 | /* tables */ 290 | .table { 291 | width: 100%; 292 | border-collapse: collapse; 293 | margin-bottom: 15px; 294 | } 295 | 296 | .table th { 297 | padding: 10px; 298 | font-weight: bold; 299 | text-align: left; 300 | } 301 | 302 | .table th.first { 303 | width: 30px; 304 | } 305 | 306 | .table th.last { 307 | width: 200px; 308 | } 309 | 310 | .table .checkbox { 311 | margin-left: 10px; 312 | } 313 | 314 | .table td { 315 | padding: 10px; 316 | } 317 | 318 | .table td.last { 319 | text-align: right; 320 | } 321 | 322 | 323 | .table th { 324 | background: #eaeaea; 325 | color: #222; 326 | font-weight: normal; 327 | } 328 | 329 | .table td { 330 | border-bottom: 1px solid #eaeaea; 331 | } 332 | 333 | .table tr.even { 334 | background: #f8f8f8; 335 | } 336 | 337 | a:link, a:visited, a:hover, a:active, h1, h2, h3 { color: #111; } 338 | a { -moz-outline: none; } 339 | 340 | .submenu { 341 | -moz-border-radius-topleft: 9px; 342 | -webkit-border-top-left-radius: 9px; 343 | -moz-border-radius-topright: 9px; 344 | -webkit-border-top-right-radius: 9px; 345 | background-color: #f2f1ee; 346 | border-bottom: none; 347 | border-bottom-width: 5px; 348 | background-image: url("/img/boxbar-background.png"); 349 | font-size: 13px; 350 | } 351 | 352 | .submenu li.first a, .submenu ul li.first { 353 | -moz-border-radius-topleft: 9px; 354 | -webkit-border-top-left-radius: 9px; 355 | } 356 | 357 | .submenu ul li.first { 358 | -moz-border-radius-topleft: 9px; 359 | -webkit-border-top-left-radius: 9px; 360 | } 361 | 362 | .submenu ul { 363 | margin: 0; 364 | padding: 0; 365 | list-style-type: none; 366 | } 367 | 368 | .submenu ul li { 369 | float: left; 370 | } 371 | 372 | .submenu ul li a:link, .submenu ul li a:visited, .submenu ul li a:hover, .submenu ul li a:active { 373 | text-decoration: none; 374 | } 375 | 376 | .submenu ul li a:hover, .submenu ul li a:active { 377 | text-decoration: underline; 378 | } 379 | 380 | .submenu ul li.active, .submenu ul li.active a:hover { 381 | background-color: #fff; 382 | } 383 | 384 | 385 | .submenu ul li a { 386 | display: block; 387 | padding: 10px 15px; 388 | } 389 | 390 | .block { 391 | -moz-border-radius: 9px; 392 | -webkit-border-radius: 9px; 393 | border-radius: 9px; 394 | -moz-border-radius-bottomleft: 9px; 395 | -webkit-border-bottom-left-radius: 9px; 396 | -moz-border-radius-bottomright: 9px; 397 | -webkit-border-bottom-right-radius: 9px; 398 | -moz-border-radius-topleft: 9px; 399 | -webkit-border-top-left-radius: 9px; 400 | -moz-border-radius-topright: 9px; 401 | -webkit-border-top-right-radius: 9px; 402 | padding-top: 0px; 403 | background: #fff; 404 | 405 | -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4); 406 | -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4); 407 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4); 408 | margin-bottom: 20px; 409 | } 410 | 411 | .block .content { 412 | padding: 0 15px 15px; 413 | } 414 | .block h1 { 415 | margin-left: 15px; 416 | font-size: 22px; 417 | font-style: normal; 418 | font-weight: bold; 419 | text-transform: none; 420 | letter-spacing: -1px; 421 | line-height: 1.2em; 422 | padding: 15px 0; 423 | } 424 | 425 | /** FRONT PAGE BOX **/ 426 | #box { 427 | -moz-border-radius-topleft: 9px; 428 | -webkit-border-top-left-radius: 9px; 429 | -moz-border-radius-topright: 9px; 430 | -webkit-border-top-right-radius: 9px; 431 | 432 | width: 610px; 433 | margin: 50px auto; 434 | } 435 | /* box */ 436 | 437 | #box .block { 438 | -moz-border-radius-topleft: 9px; 439 | -webkit-border-top-left-radius: 9px; 440 | -moz-border-radius-topright: 9px; 441 | -webkit-border-top-right-radius: 9px; 442 | 443 | background: #fff; 444 | 445 | -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4); 446 | -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4); 447 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.4); 448 | -moz-border-radius-bottomleft: 9px; 449 | -webkit-border-bottom-left-radius: 9px; 450 | -moz-border-radius-bottomright: 9px; 451 | -webkit-border-bottom-right-radius: 9px; 452 | } 453 | 454 | #box .block h1 { 455 | -moz-border-radius-topleft: 9px; 456 | -webkit-border-top-left-radius: 9px; 457 | -moz-border-radius-topright: 9px; 458 | -webkit-border-top-right-radius: 9px; 459 | 460 | background: black; 461 | color: #fff; 462 | 463 | padding: 10px 23px; 464 | margin: 0; 465 | 466 | } 467 | 468 | #box .block .content { 469 | padding: 10px 20px; 470 | } 471 | 472 | input.twothirds { 473 | width: 280px; 474 | } 475 | 476 | input.half { 477 | width: 210px; 478 | } 479 | 480 | /* pagination */ 481 | 482 | .pagination { 483 | padding-bottom: 5px; 484 | } 485 | 486 | .pagination a, .pagination span { 487 | background-color: #f4f4f4; 488 | background-image: url("/img/button-background.png"); 489 | color: #111; 490 | text-align: center; 491 | min-width: 15px; 492 | margin-right: 5px; 493 | padding: 6px; 494 | border: 1px solid #c3c4ba; 495 | } 496 | 497 | .pagination em { 498 | background: #002134; 499 | color: #fff; 500 | border: 1px solid #002134; 501 | font-weight: bold; 502 | } 503 | 504 | .pagination a { 505 | color: #1a1a1a; 506 | text-decoration: none; 507 | } 508 | 509 | .pagination a:hover { 510 | border: 1px solid #818171; 511 | -webkit-box-shadow: 0 0 4px rgba(0, 0, 0, 0.3); 512 | -moz-box-shadow: 0 0 4px rgba(0, 0, 0, 0.3); 513 | box-shadow: 0 0 4px rgba(0, 0, 0, 0.3); 514 | } 515 | 516 | .pagination a:active { 517 | /* background-image: url("/img/button-background-active.png"); */ 518 | outline: none; 519 | } 520 | /* pagination */ 521 | 522 | .pagination a, .pagination span, .pagination em { 523 | padding: 2px 5px; 524 | margin-right: 5px; 525 | display: block; 526 | float: left; 527 | border-style: solid; 528 | border-width: 1px; 529 | } 530 | 531 | 532 | .pagination a.disabled, .pagination span.disabled { 533 | color: #818171; 534 | } 535 | 536 | /* front page login links */ 537 | a.login_provider { 538 | width: 100px; 539 | height: 60px; 540 | border: 1px solid #DDD; 541 | margin: 3px; 542 | float: left; 543 | } 544 | 545 | a.login_provider:hover { 546 | border: 1px solid black; 547 | } 548 | 549 | /* profile page: associated accounts */ 550 | 551 | a.associated_account { 552 | width: 32px; height: 32px; margin: 3px; float: left; 553 | } 554 | 555 | a.facebook { 556 | background: #FFF url(/img/small/facebook_gray.png) no-repeat center center; 557 | } 558 | 559 | a.facebook:hover { 560 | background: #FFF url(/img/small/facebook.png) no-repeat center center; 561 | } 562 | 563 | a.twitter { 564 | background: #FFF url(/img/small/twitter_gray.png) no-repeat center center; 565 | } 566 | a.twitter:hover { 567 | background: #FFF url(/img/small/twitter.png) no-repeat center center; 568 | } 569 | a.google { 570 | background: #FFF url(/img/small/google_gray.png) no-repeat center center 571 | } 572 | a.google:hover { 573 | background: #FFF url(/img/small/google.png) no-repeat center center 574 | } 575 | a.yahoo { 576 | background: #FFF url(/img/small/yahoo_gray.png) no-repeat center center; 577 | } 578 | a.yahoo:hover { 579 | background: #FFF url(/img/small/yahoo.png) no-repeat center center; 580 | } 581 | 582 | 583 | -------------------------------------------------------------------------------- /public/img/boxbar-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixu/useradmin/458bedd41c73743395d9350c9cf766b862c55e81/public/img/boxbar-background.png -------------------------------------------------------------------------------- /public/img/button-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixu/useradmin/458bedd41c73743395d9350c9cf766b862c55e81/public/img/button-background.png -------------------------------------------------------------------------------- /public/img/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixu/useradmin/458bedd41c73743395d9350c9cf766b862c55e81/public/img/facebook.png -------------------------------------------------------------------------------- /public/img/fb-login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixu/useradmin/458bedd41c73743395d9350c9cf766b862c55e81/public/img/fb-login.png -------------------------------------------------------------------------------- /public/img/google.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixu/useradmin/458bedd41c73743395d9350c9cf766b862c55e81/public/img/google.gif -------------------------------------------------------------------------------- /public/img/linkedin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixu/useradmin/458bedd41c73743395d9350c9cf766b862c55e81/public/img/linkedin.png -------------------------------------------------------------------------------- /public/img/menubar-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixu/useradmin/458bedd41c73743395d9350c9cf766b862c55e81/public/img/menubar-background.png -------------------------------------------------------------------------------- /public/img/small/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixu/useradmin/458bedd41c73743395d9350c9cf766b862c55e81/public/img/small/facebook.png -------------------------------------------------------------------------------- /public/img/small/facebook_gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixu/useradmin/458bedd41c73743395d9350c9cf766b862c55e81/public/img/small/facebook_gray.png -------------------------------------------------------------------------------- /public/img/small/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixu/useradmin/458bedd41c73743395d9350c9cf766b862c55e81/public/img/small/google.png -------------------------------------------------------------------------------- /public/img/small/google_gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixu/useradmin/458bedd41c73743395d9350c9cf766b862c55e81/public/img/small/google_gray.png -------------------------------------------------------------------------------- /public/img/small/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixu/useradmin/458bedd41c73743395d9350c9cf766b862c55e81/public/img/small/twitter.png -------------------------------------------------------------------------------- /public/img/small/twitter_gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixu/useradmin/458bedd41c73743395d9350c9cf766b862c55e81/public/img/small/twitter_gray.png -------------------------------------------------------------------------------- /public/img/small/yahoo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixu/useradmin/458bedd41c73743395d9350c9cf766b862c55e81/public/img/small/yahoo.png -------------------------------------------------------------------------------- /public/img/small/yahoo_gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixu/useradmin/458bedd41c73743395d9350c9cf766b862c55e81/public/img/small/yahoo_gray.png -------------------------------------------------------------------------------- /public/img/tiny/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixu/useradmin/458bedd41c73743395d9350c9cf766b862c55e81/public/img/tiny/facebook.png -------------------------------------------------------------------------------- /public/img/tiny/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixu/useradmin/458bedd41c73743395d9350c9cf766b862c55e81/public/img/tiny/google.png -------------------------------------------------------------------------------- /public/img/tiny/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixu/useradmin/458bedd41c73743395d9350c9cf766b862c55e81/public/img/tiny/twitter.png -------------------------------------------------------------------------------- /public/img/tiny/yahoo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixu/useradmin/458bedd41c73743395d9350c9cf766b862c55e81/public/img/tiny/yahoo.png -------------------------------------------------------------------------------- /public/img/twitter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixu/useradmin/458bedd41c73743395d9350c9cf766b862c55e81/public/img/twitter.png -------------------------------------------------------------------------------- /public/img/yahoo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixu/useradmin/458bedd41c73743395d9350c9cf766b862c55e81/public/img/yahoo.gif -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | TUTORIAL 3 | ======== 4 | 5 | NEW: Upgraded to support Kohana 3.1.x! Thank you gartz for this! If you need KO 3.0.x support, use the Feb 27th version. 6 | 7 | Most recent, detailed tutorial - note, documentation still needs an update: 8 | 9 | * [Getting started with Useradmin, my Kohana 3 auth admin module](http://blog.mixu.net/2011/01/13/getting-started-with-useradmin-my-kohana-3-auth-admin-module/) 10 | 11 | Earlier writing about Kohana auth: 12 | 13 | * [Kohana 3 auth sample implementation and documentation](http://blog.mixu.net/2010/09/14/kohana-3-auth-sample-implementation-and-documentation/) 14 | * [Kohana 3 auth: the auth module functionality](http://blog.mixu.net/2010/09/07/kohana-3-auth-the-auth-module-functionality/) 15 | * [Step-by-step guide to getting started with Kohana 3 auth](http://blog.mixu.net/2010/09/06/step-by-step-guide-to-kohana-3-auth/) 16 | 17 | SUPPORTED PROVIDERS 18 | =================== 19 | 20 | + Regular Kohana user account registration 21 | + Facebook (OAuth v2.0) 22 | + Twitter (OAuth v1.0) 23 | + Google (OpenID) 24 | + Yahoo (OpenID) 25 | 26 | MODULES USED 27 | ============ 28 | 29 | * Core: Auth, ORM, Database 30 | * Core (but not included by default in KO 3.1): pagination 31 | * Other: kohana-email, kohana-oauth, fleepme-unittest. 32 | 33 | CONTRIBUTORS 34 | ============ 35 | 36 | I would like to thank: 37 | 38 | * [jnbdz](https://bitbucket.org/jnbdz/useradmin/) for adding the reCaptcha support. 39 | * [gartz](https://github.com/gartz/useradmin/) for doing upgrading the module to support Kohana 3.1.x. 40 | 41 | CHANGELOG 42 | ========= 43 | 44 | March 19th 2011: 45 | 46 | * Upgraded to support Kohana 3.1.x! Thanks gartz! 47 | * Testing various actions to ensure KO 3.1.2 compatibility (admin in particular), improved validation message handling. 48 | * Added support for media file routing to make it easier to get started with the module. 49 | 50 | Feb 27th 2011: 51 | 52 | * New UI! The UI is loosely based on https://github.com/pilu/web-app-theme (a theme generator for Ruby) and is a lot better looking than the old one. See screenshots below. 53 | * Twitter login! Requires that you enable the kohana-oauth module from the core. 54 | * Google and Yahoo login! Uses LightOpenID which is bundled in the repo. I had a look at Janrain's popular library, but to me if you let your library generate errors on PHP 5.0.0 - 5.3.0 and have dozens of pending issues and pull requests - then you just don't care about supporting your library. So not using Janrain's lib. 55 | * Refactored 3rd party account provider code for better extensibility. 56 | * Changed from Facebook Javascript API (which had some reports of problems) to PHP/redirect based API. No popups during login, just a redirect! 57 | * Database changes to allow user to be associated with more service providers. See MIGRATION below if you used had the Facebook login enabled previously and want to migrate the accounts. 58 | * Included Datatable helper which helps when working with pagination links in tables. 59 | 60 | Feb 21st 2011: 61 | 62 | * CRITICAL SECURITY BUG in Facebook login; it is strongly recommended that all users of Facebook login functionality upgrade (does not affect sites with Facebook login disabled). Thank you Euan McKay! 63 | * To support login via AJAX requests moved the processing of results from the Auth check to Controller_App->access_required and Controller_App->login_required. Override those in your app, using Request::$is_ajax to detect AJAX requests. 64 | * Workaround for email field for http://dev.kohanaframework.org/issues/3750 in messages/register.php 65 | 66 | Jan 28th 2011: 67 | 68 | * Added reCaptcha support (captcha on registration) 69 | * Upgraded to Kohana v3.0.9 70 | 71 | Jan 12th 2011: Pushed out new version with following improvements: 72 | 73 | * Facebook login! Yes, you can now have users register and log in using their Facebook account. 74 | * New tutorial! I tried to go through everything necessary to get started with the module, see this new post. 75 | * Transparent extending is now supported; you can start implementing your application by overriding Controller_User / Model_User with a class that extends Controller_Useradmin_User / Model_Useradmin_User. 76 | * Bug fixes (login using email, validation messages, saving empty password) 77 | 78 | Nov 25th 2010: Pushed out new version with following improvements: 79 | 80 | * Now includes Kohana-Email module for sending password reset emails (users can request password request by entering an email, and they will be sent a one-time reset token via email). 81 | * Validation code in model has been improved based on feedback from biakaveron. 82 | * Validation code is prettier: it uses the approach from my validation-with-forms tutorial. 83 | * Framework has been updated to v3.0.8 and minor compatibility bugs fixed. 84 | 85 | Sep 14th 2010: 86 | 87 | * Initial version. 88 | 89 | SCREENSHOT 90 | ========== 91 | 92 | ![screenshot](https://github.com/mixu/useradmin/raw/master/useradmin-screen.png) 93 | 94 | MIGRATION from pre-Feb xxth 2011 to new schema 95 | ============================================== 96 | 97 | The schema had to be updated to allow for better support for multiple 3rd party providers. 98 | 99 | Basically, the facebook_user_id field has been removed from the users table, and moved 100 | to the user_identies table. To migrate, you need to create a row for each user with a facebook_user_id 101 | in that table, something like: 102 | 103 | INSERT INTO `useradmin`.`user_identities` (`user_id`, `provider`, `identity`) SELECT `users`.`id`, 104 | "facebook", `users`.`facebook_user_id` FROM `useradmin`.`users`; 105 | 106 | after creating the new table. You can drop `facebook_user_id` after this. 107 | 108 | LICENCE (Simplified BSD licence) 109 | 110 | NOTE: This licence applies to the useradmin module only, as written by Mikito 111 | Takada (found under /modules/user/). Different licences may apply to the other 112 | modules in this repository and the Kohana 3 core. 113 | 114 | ------- 115 | 116 | Copyright 2010 Mikito Takada. All rights reserved. 117 | 118 | Redistribution and use in source and binary forms, with or without modification, are 119 | permitted provided that the following conditions are met: 120 | 121 | 1. Redistributions of source code must retain the above copyright notice, this list of 122 | conditions and the following disclaimer. 123 | 124 | 2. Redistributions in binary form must reproduce the above copyright notice, this list 125 | of conditions and the following disclaimer in the documentation and/or other materials 126 | provided with the distribution. 127 | 128 | THIS SOFTWARE IS PROVIDED BY MIKITO TAKADA ``AS IS'' AND ANY EXPRESS OR IMPLIED 129 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 130 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MIKITO TAKADA OR 131 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 132 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 133 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 134 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 135 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 136 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 137 | 138 | The views and conclusions contained in the software and documentation are those of the 139 | authors and should not be interpreted as representing official policies, either expressed 140 | or implied, of Mikito Takada. 141 | 142 | ------- 143 | 144 | Not that I have any official policies. 145 | 146 | DEPENDENCIES'S LICENCES (bundled in repository; don't have to find these) 147 | ========================================================================= 148 | I have attempted to ensure that none of the dependencies are GPL licenced, 149 | so that they can be used in commercial applications without worrying about copyleft. 150 | 151 | Vendors folder: 152 | 153 | - Facebook's PHP SDK is under the Apache Licence, v.2.0 - needed for Facebook. 154 | - LightOpenID is under MIT licence - needed for OpenID (Google, Yahoo). 155 | - ReCaptchalib is under MIT licence - needed for ReCaptcha. 156 | 157 | Kohana modules: 158 | 159 | - Kohana oAuth and Auth are part of the Kohana Core. 160 | - Kohana-email is a port of the same module which was in the Kohana v.2.x Core. 161 | It depends on Swiftmailer which is LGPL. 162 | 163 | Icons: 164 | 165 | - http://www.nouveller.com/general/free-social-media-bookmark-icon-pack-the-ever-growing-icon-set/ 166 | Quote: "[Y]ou can use them anywhere, for anything for free with no attribution. No strings attached." 167 | 168 | Please consult a lawyer if you need legal advice. 169 | -------------------------------------------------------------------------------- /schema.sql: -------------------------------------------------------------------------------- 1 | -- MySQL dump 10.13 Distrib 5.5.8, for Linux (x86_64) 2 | -- 3 | -- Host: localhost Database: useradmin 4 | -- ------------------------------------------------------ 5 | -- Server version 5.5.8 6 | 7 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 8 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 9 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 10 | /*!40101 SET NAMES utf8 */; 11 | /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; 12 | /*!40103 SET TIME_ZONE='+00:00' */; 13 | /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; 14 | /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; 15 | /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; 16 | /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; 17 | 18 | -- 19 | -- Table structure for table `roles` 20 | -- 21 | 22 | DROP TABLE IF EXISTS `roles`; 23 | /*!40101 SET @saved_cs_client = @@character_set_client */; 24 | /*!40101 SET character_set_client = utf8 */; 25 | CREATE TABLE `roles` ( 26 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 27 | `name` varchar(32) NOT NULL, 28 | `description` varchar(255) NOT NULL, 29 | PRIMARY KEY (`id`), 30 | UNIQUE KEY `uniq_name` (`name`) 31 | ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8; 32 | /*!40101 SET character_set_client = @saved_cs_client */; 33 | 34 | -- 35 | -- Dumping data for table `roles` 36 | -- 37 | 38 | INSERT INTO `roles` (`id`, `name`, `description`) VALUES 39 | (1, 'login', 'Login privileges, granted after account confirmation'), 40 | (2, 'admin', 'Administrative user, has access to everything.'); 41 | 42 | -- -------------------------------------------------------- 43 | 44 | -- 45 | -- Table structure for table `roles_users` 46 | -- 47 | 48 | DROP TABLE IF EXISTS `roles_users`; 49 | /*!40101 SET @saved_cs_client = @@character_set_client */; 50 | /*!40101 SET character_set_client = utf8 */; 51 | CREATE TABLE `roles_users` ( 52 | `user_id` int(10) unsigned NOT NULL, 53 | `role_id` int(10) unsigned NOT NULL, 54 | PRIMARY KEY (`user_id`,`role_id`), 55 | KEY `fk_role_id` (`role_id`), 56 | CONSTRAINT `roles_users_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE, 57 | CONSTRAINT `roles_users_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`) ON DELETE CASCADE 58 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 59 | /*!40101 SET character_set_client = @saved_cs_client */; 60 | 61 | -- 62 | -- Dumping data for table `roles_users` 63 | -- 64 | 65 | INSERT INTO `roles_users` (`user_id`, `role_id`) VALUES 66 | (1, 1), 67 | (1, 2); 68 | 69 | -- -------------------------------------------------------- 70 | 71 | -- 72 | -- Table structure for table `user_identity` 73 | -- 74 | 75 | DROP TABLE IF EXISTS `user_identity`; 76 | /*!40101 SET @saved_cs_client = @@character_set_client */; 77 | /*!40101 SET character_set_client = utf8 */; 78 | CREATE TABLE `user_identity` ( 79 | `id` int(11) NOT NULL AUTO_INCREMENT, 80 | `user_id` int(11) NOT NULL, 81 | `provider` varchar(255) NOT NULL, 82 | `identity` varchar(255) NOT NULL, 83 | PRIMARY KEY (`id`) 84 | ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1; 85 | /*!40101 SET character_set_client = @saved_cs_client */; 86 | 87 | -- 88 | -- Table structure for table `user_tokens` 89 | -- 90 | 91 | DROP TABLE IF EXISTS `user_tokens`; 92 | /*!40101 SET @saved_cs_client = @@character_set_client */; 93 | /*!40101 SET character_set_client = utf8 */; 94 | CREATE TABLE `user_tokens` ( 95 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 96 | `user_id` int(11) unsigned NOT NULL, 97 | `user_agent` varchar(40) NOT NULL, 98 | `token` varchar(40) NOT NULL, 99 | `created` int(10) unsigned NOT NULL, 100 | `expires` int(10) unsigned NOT NULL, 101 | PRIMARY KEY (`id`), 102 | UNIQUE KEY `uniq_token` (`token`), 103 | KEY `fk_user_id` (`user_id`), 104 | CONSTRAINT `user_tokens_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE 105 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 106 | /*!40101 SET character_set_client = @saved_cs_client */; 107 | 108 | -- 109 | -- Table structure for table `users` 110 | -- 111 | 112 | DROP TABLE IF EXISTS `users`; 113 | /*!40101 SET @saved_cs_client = @@character_set_client */; 114 | /*!40101 SET character_set_client = utf8 */; 115 | CREATE TABLE `users` ( 116 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 117 | `email` varchar(127) NOT NULL, 118 | `username` varchar(32) NOT NULL DEFAULT '', 119 | `password` char(64) NOT NULL, 120 | `logins` int(10) unsigned NOT NULL DEFAULT '0', 121 | `last_login` int(10) unsigned DEFAULT NULL, 122 | `reset_token` char(64) NOT NULL DEFAULT '', 123 | `status` varchar(20) NOT NULL DEFAULT '', 124 | `last_failed_login` datetime NOT NULL, 125 | `failed_login_count` int(11) NOT NULL DEFAULT '0', 126 | `created` datetime NOT NULL, 127 | `modified` datetime NOT NULL, 128 | PRIMARY KEY (`id`), 129 | UNIQUE KEY `uniq_username` (`username`), 130 | UNIQUE KEY `uniq_email` (`email`) 131 | ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8; 132 | /*!40101 SET character_set_client = @saved_cs_client */; 133 | /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; 134 | 135 | -- 136 | -- Dumping data for table `users` 137 | -- 138 | 139 | INSERT INTO `users` (`id`, `email`, `username`, `password`, `logins`, `last_login`) VALUES 140 | (1, 'test@test.com', 'admin', '368ae03c1b3b29b8d242bc43dcbe3f0bd4755ea181adbd22ef', 0, NULL); 141 | 142 | /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; 143 | /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; 144 | /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; 145 | /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; 146 | /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; 147 | /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; 148 | /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; 149 | 150 | -- Dump completed on 2011-02-23 21:25:17 151 | -------------------------------------------------------------------------------- /tests/data/clean-setup.sql: -------------------------------------------------------------------------------- 1 | ../../schema.sql -------------------------------------------------------------------------------- /tests/usertests.php: -------------------------------------------------------------------------------- 1 | 13 | * @copyright (c) 2011-2011 Fleep.me 14 | */ 15 | class UserTests extends Unittest_Model_TestCase { 16 | 17 | private function add_valid_users() 18 | { 19 | $users_provider = $this->providerValidUsers(); 20 | $users = array(); 21 | $i = 0; 22 | foreach ($users_provider as $user) 23 | { 24 | try 25 | { 26 | $users[$i] = Model::factory("user") 27 | ->create_user($user[0], array( 28 | "username", 29 | "email", 30 | "password", 31 | )); 32 | 33 | $login_role = new Model_Role(array('name' =>'login')); 34 | $users[$i]->add('roles', $login_role); 35 | } 36 | catch (Kohana_Exception $e) 37 | { 38 | $this->fail("Can't create user: ".Kohana_Debug::dump($e)); 39 | } 40 | $i++; 41 | } 42 | return $users; 43 | } 44 | 45 | /** 46 | * Valid users Provider 47 | */ 48 | public function providerValidUsers() 49 | { 50 | return array( 51 | // Basic username 52 | array(array( 53 | "username" => "validuser", 54 | "password" => "123456", 55 | "password_confirm" => "123456", 56 | "email" => "valid@user.com", 57 | )), 58 | // Caps username 59 | array(array( 60 | "username" => "MyOwnUser", 61 | "password" => "ab38cab38c", 62 | "password_confirm" => "ab38cab38c", 63 | "email" => "MyOwn@User.com", 64 | )), 65 | // Underline username 66 | array(array( 67 | "username" => "separate_user", 68 | "password" => "verylongpasswordwith@chars!dix97", 69 | "password_confirm" => "verylongpasswordwith@chars!dix97", 70 | "email" => "separate@User.com", 71 | )), 72 | // Single char username 73 | array(array( 74 | "username" => "a", 75 | "password" => "verylongpasswordwith@chars!dix97", 76 | "password_confirm" => "verylongpasswordwith@chars!dix97", 77 | "email" => "a@User.com", 78 | )), 79 | ); 80 | } 81 | 82 | /** 83 | * Valid user updates Provider 84 | */ 85 | public function providerValidUserUpdates() 86 | { 87 | return array( 88 | // #0: Update email 89 | array( 90 | // login 91 | array( 92 | "username" => "validuser", 93 | "password" => "123456", 94 | ), 95 | // fields 96 | array( 97 | "username" => "validuser", 98 | "email" => "valid_changed@user.com", 99 | ), 100 | ), 101 | // #1: Update username 102 | array( 103 | // login 104 | array( 105 | "username" => "validuser", 106 | "password" => "123456", 107 | ), 108 | // fields 109 | array( 110 | "username" => "validuser_changed", 111 | "email" => "valid_changed@user.com", 112 | ), 113 | ), 114 | // #2: Update password 115 | array( 116 | // login 117 | array( 118 | "username" => "validuser", 119 | "password" => "123456", 120 | ), 121 | // fields 122 | array( 123 | "password" => "123456789", 124 | "password_confirm" => "123456789", 125 | ), 126 | ), 127 | // #3: Update nothing 128 | array( 129 | // login 130 | array( 131 | "username" => "validuser", 132 | "password" => "123456", 133 | ), 134 | // fields 135 | array( 136 | ), 137 | ), 138 | // Loop 139 | ); 140 | } 141 | 142 | /** 143 | * Invalid user updates Provider 144 | */ 145 | public function providerInvalidUserUpdates() 146 | { 147 | return array( 148 | // #0: Short password 149 | array( 150 | // login 151 | array( 152 | "username" => "MyOwnUser", 153 | "password" => "ab38cab38c", 154 | ), 155 | // fields 156 | array( 157 | "password" => "12345", 158 | "password_confirm" => "12345", 159 | ), 160 | ), 161 | // #1: Wrong password confirmation 162 | array( 163 | // login 164 | array( 165 | "username" => "MyOwnUser", 166 | "password" => "ab38cab38c", 167 | ), 168 | // fields 169 | array( 170 | "password" => "123456", 171 | "password_confirm" => "654321", 172 | ), 173 | ), 174 | // #2: Used username 175 | array( 176 | // login 177 | array( 178 | "username" => "MyOwnUser", 179 | "password" => "ab38cab38c", 180 | ), 181 | // fields 182 | array( 183 | "username" => "A", 184 | ), 185 | ), 186 | // #3: Used email 187 | array( 188 | // login 189 | array( 190 | "username" => "MyOwnUser", 191 | "password" => "ab38cab38c", 192 | ), 193 | // fields 194 | array( 195 | "email" => "a@User.com", 196 | ), 197 | ), 198 | // #4: Username with spaces 199 | array( 200 | // login 201 | array( 202 | "username" => "MyOwnUser", 203 | "password" => "ab38cab38c", 204 | ), 205 | // fields 206 | array( 207 | "username" => "name with spaces", 208 | ), 209 | ), 210 | // #5: Username with invalid char 211 | array( 212 | // login 213 | array( 214 | "username" => "MyOwnUser", 215 | "password" => "ab38cab38c", 216 | ), 217 | // fields 218 | array( 219 | "username" => "name with spaces", 220 | ), 221 | ), 222 | // #6: Blank username 223 | array( 224 | // login 225 | array( 226 | "username" => "MyOwnUser", 227 | "password" => "ab38cab38c", 228 | ), 229 | // fields 230 | array( 231 | "username" => "", 232 | ), 233 | ), 234 | // #7: Too long username 235 | array( 236 | // login 237 | array( 238 | "username" => "MyOwnUser", 239 | "password" => "ab38cab38c", 240 | ), 241 | // fields 242 | array( 243 | "username" => "anamewithtoomanycharacterscantwork", 244 | ), 245 | ), 246 | // #8: Invalid e-mail 247 | array( 248 | // login 249 | array( 250 | "username" => "MyOwnUser", 251 | "password" => "ab38cab38c", 252 | ), 253 | // fields 254 | array( 255 | "email" => "anamewithtoo@manychar@aar.sck", 256 | ), 257 | ), 258 | // #9: Used username with space 259 | array( 260 | // login 261 | array( 262 | "username" => "MyOwnUser", 263 | "password" => "ab38cab38c", 264 | ), 265 | // fields 266 | array( 267 | "username" => "A ", 268 | ), 269 | ), 270 | // Loop 271 | ); 272 | } 273 | 274 | /** 275 | * Invalid users Provider 276 | */ 277 | public function providerInvalidUsers() 278 | { 279 | return array( 280 | // Userame with spaces 281 | array(array( 282 | "username" => "name with spaces", 283 | "password" => "123456789", 284 | "password_confirm" => "123456789", 285 | "email" => "email with spaces@user.com", 286 | )), 287 | // Userame with a invalid char 288 | array(array( 289 | "username" => "namewith@char", 290 | "password" => "ab38cab38c", 291 | "password_confirm" => "ab38cab38c", 292 | "email" => "namewith@char@User.com", 293 | )), 294 | // Blank username 295 | array(array( 296 | "username" => "", 297 | "password" => "verylongpasswordwith@chars!dix97", 298 | "password_confirm" => "verylongpasswordwith@chars!dix97", 299 | "email" => "@User.com", 300 | )), 301 | // Too long username 302 | array(array( 303 | "username" => "anamewithtoomanycharacterscantwork", 304 | "password" => "verylongpasswordwith@chars!dix97", 305 | "password_confirm" => "verylongpasswordwith@chars!dix97", 306 | "email" => "anamewithtoomanycharacterscantwork@User.com", 307 | )), 308 | // No password 309 | array(array( 310 | "username" => "validusername2", 311 | "password" => "", 312 | "password_confirm" => "", 313 | "email" => "validusername2@User.com", 314 | )), 315 | // No confirm password 316 | array(array( 317 | "username" => "validusername3", 318 | "password" => "123456789", 319 | "password_confirm" => "", 320 | "email" => "validusername3@User.com", 321 | )), 322 | // Diferent confirm password 323 | array(array( 324 | "username" => "validusername4", 325 | "password" => "123456789", 326 | "password_confirm" => "987654321", 327 | "email" => "validusername4@User.com", 328 | )), 329 | // Too short Password 330 | array(array( 331 | "username" => "validusername6", 332 | "password" => "12345", 333 | "password_confirm" => "12345", 334 | "email" => "validusername6@User.com", 335 | )), 336 | // Invalid e-mail 337 | array(array( 338 | "username" => "validusername5", 339 | "password" => "123456789", 340 | "password_confirm" => "123456789", 341 | "email" => "validu@sername5@User.com", 342 | )), 343 | // Already registred user with diferente case 344 | // array(array( 345 | // "username" => "A", 346 | // "password" => "123456789", 347 | // "password_confirm" => "123456789", 348 | // "email" => "a_new@User.com", 349 | // )), 350 | ); 351 | } 352 | 353 | /** 354 | * test if auth exists 355 | * @author Gabriel Giannattasio 356 | * @test 357 | */ 358 | public function test_if_auth_exists() 359 | { 360 | $this->assertTrue( class_exists("Auth"), 'Auth class not found' ); 361 | } // test if auth is a class 362 | 363 | /** 364 | * test auth register user 365 | * @author Gabriel Giannattasio 366 | * @test 367 | * @dataProvider providerValidUsers 368 | * @depends test_if_auth_exists 369 | */ 370 | public function test_auth_register_valid_users( $fields ) 371 | { 372 | UserTests::useTestDB(); 373 | $status = TRUE; 374 | try 375 | { 376 | Auth::instance()->register($fields); 377 | } catch (Exception $e) { 378 | $status = FALSE; 379 | } 380 | $this->assertTrue( $status, 'Must be a valid user.'); 381 | } 382 | 383 | /** 384 | * test auth register invalid users 385 | * @author Gabriel Giannattasio 386 | * @test 387 | * @dataProvider providerInvalidUsers 388 | * @depends test_if_auth_exists 389 | */ 390 | public function test_auth_register_invalid_users( $fields ) 391 | { 392 | try 393 | { 394 | $status = Auth::instance()->register($fields); 395 | $this->fail("Register a invalid user: ".Kohana_Debug::dump($fields)); 396 | } 397 | catch (ORM_Validation_Exception $e) 398 | { 399 | $this->assertInstanceOf("ORM_Validation_Exception",$e); 400 | return; 401 | } 402 | catch (Exception $e) 403 | { 404 | $this->fail("Unspected exception: ".$e->getMessage()); 405 | } 406 | } 407 | 408 | /** 409 | * test auth register invalid fields 410 | * @author Gabriel Giannattasio 411 | * @test 412 | * @depends test_if_auth_exists 413 | */ 414 | public function test_auth_register_invalid_fields() 415 | { 416 | $registerFail = FALSE; 417 | try { 418 | Auth::instance()->register(NULL); 419 | } catch (Exception $e) { 420 | $registerFail = TRUE; 421 | } 422 | $this->assertTrue($registerFail); 423 | } 424 | 425 | /** 426 | * test auth valid logins and logout 427 | * @author Gabriel Giannattasio 428 | * @test 429 | * @dataProvider providerValidUsers 430 | */ 431 | public function test_auth_valid_logins_and_logout( $fields ) 432 | { 433 | // Setup valid users 434 | $this->add_valid_users(); 435 | 436 | $this->assertTrue( Auth::instance()->logout(TRUE, TRUE), "Force logout all" ); 437 | $this->assertTrue( Auth::instance()->login( $fields['username'], $fields['password']) , "Check login using username"); 438 | $this->assertTrue( Auth::instance()->login( $fields['email'], $fields['password']) , "Check login using email"); 439 | // Get the user model 440 | $user = Auth::instance()->get_user(); 441 | $this->assertInstanceOf("Model_User", $user, "Checking the Model_User instance"); 442 | $this->assertSame($fields['username'], $user->username, "Check the logedin username"); 443 | $this->assertTrue( Auth::instance()->logout() ); 444 | } 445 | 446 | /** 447 | * test auth valid logins and update_user 448 | * @author Gabriel Giannattasio 449 | * @test 450 | * @dataProvider providerValidUserUpdates 451 | */ 452 | public function test_auth_valid_logins_and_update_user( $login, $fields ) 453 | { 454 | // Setup valid users 455 | $this->add_valid_users(); 456 | 457 | $this->assertTrue( Auth::instance()->logout(TRUE, TRUE), "Force logout all" ); 458 | $this->assertTrue( Auth::instance()->login( $login['username'], $login['password']), "Do the login" ); 459 | $user = Auth::instance()->get_user(); 460 | try 461 | { 462 | $user->update_user( $fields, array( 463 | 'username', 464 | 'password', 465 | 'email', 466 | ) ); 467 | $status = TRUE; 468 | } 469 | catch (ORM_Validation_Exception $e) 470 | { 471 | $status = FALSE; 472 | } 473 | 474 | $this->assertTrue( $status, "Do the user update"); 475 | } 476 | 477 | /** 478 | * test auth valid logins and invalid update_user 479 | * @author Gabriel Giannattasio 480 | * @test 481 | * @dataProvider providerInvalidUserUpdates 482 | */ 483 | public function test_auth_valid_logins_and_invalid_update_user( $login, $fields ) 484 | { 485 | // Setup valid users 486 | $this->add_valid_users(); 487 | 488 | $this->assertTrue( Auth::instance()->logout(TRUE, TRUE), "Force logout all" ); 489 | $this->assertTrue( Auth::instance()->login( $login['username'], $login['password']), "Do the login" ); 490 | $user = Auth::instance()->get_user(); 491 | try 492 | { 493 | $user->update_user( $fields, array( 494 | 'username', 495 | 'password', 496 | 'email', 497 | ) ); 498 | $status = TRUE; 499 | } 500 | catch (ORM_Validation_Exception $e) 501 | { 502 | $status = FALSE; 503 | } 504 | 505 | $this->assertFalse( $status, "Do the user update"); 506 | } 507 | 508 | /** 509 | * test auth invalid logins and logout 510 | * @author Gabriel Giannattasio 511 | * @test 512 | * @dataProvider providerInvalidUsers 513 | * @depends test_auth_register_invalid_users 514 | */ 515 | public function test_auth_invalid_logins_and_logout( $fields ) 516 | { 517 | $this->assertTrue( Auth::instance()->logout(TRUE, TRUE), "Force logout all" ); 518 | $this->assertFalse( Auth::instance()->login( $fields['username'], $fields['password']), "Check login using username" ); 519 | $this->assertFalse( Auth::instance()->login( $fields['email'], $fields['password']), "Check login using email" ); 520 | $this->assertFalse( Auth::instance()->get_user(), "get_user must fail."); 521 | $this->assertTrue( Auth::instance()->logout() ); 522 | } 523 | 524 | /** 525 | * test auth login jail 526 | * @author Gabriel Giannattasio 527 | * @test 528 | * @depends test_auth_register_valid_users 529 | */ 530 | public function test_auth_login_jail() 531 | { 532 | $this->assertTrue( Auth::instance()->logout(TRUE, TRUE), "Force logout all" ); 533 | $validUser = $this->providerValidUsers(); 534 | // Must fail 5 times before jail the user for 5 minutes 535 | $this->assertFalse( Auth::instance()->login( $validUser[2][0]['username'], "*********"), "Check login using wrong password #1" ); 536 | $this->assertFalse( Auth::instance()->login( $validUser[2][0]['username'], "*********"), "Check login using wrong password #2" ); 537 | $this->assertFalse( Auth::instance()->login( $validUser[2][0]['username'], "*********"), "Check login using wrong password #3" ); 538 | $this->assertFalse( Auth::instance()->login( $validUser[2][0]['username'], "*********"), "Check login using wrong password #4" ); 539 | $this->assertFalse( Auth::instance()->login( $validUser[2][0]['username'], "*********"), "Check login using wrong password #5" ); 540 | // Try to login with the correct password must fail 541 | $this->assertFalse( Auth::instance()->login( $validUser[2][0]['username'], $validUser[2][0]['password']), "Check login using correct password" ); 542 | } 543 | 544 | /** 545 | * test auth delete loged user 546 | * @author Gabriel Giannattasio 547 | * @test 548 | */ 549 | public function test_auth_delete_loged_user() 550 | { 551 | // Setup valid users 552 | $this->add_valid_users(); 553 | 554 | $validUser = $this->providerValidUserUpdates(); 555 | $this->assertTrue( Auth::instance()->login( $validUser[3][0]['username'], $validUser[3][0]['password']), "Do the login" ); 556 | $this->assertInstanceOf("Model_User", $user = Auth::instance()->get_user(), "Get Model_User instance form Auth"); 557 | $this->assertNull( Auth::instance()->unregister($user), "Delete the logged user"); 558 | $this->assertNull( Auth::instance()->logged_in(), "The user was deleted, why this isn't Null?"); 559 | } 560 | 561 | /** 562 | * test auth delete multiple users 563 | * @author Gabriel Giannattasio 564 | * @test 565 | */ 566 | public function test_auth_delete_multiple_users() 567 | { 568 | // Setup valid users 569 | $this->add_valid_users(); 570 | 571 | $validUsers = $this->providerValidUsers(); 572 | array_walk ($validUsers, function(&$user) 573 | { 574 | $username = $user[0]['username']; 575 | $user = new Model_User(); 576 | $user->where("username","=", $username) 577 | ->find(); 578 | }); 579 | $this->assertNull( Auth::instance()->unregister($validUsers), "Delete the users in array"); 580 | foreach ($validUsers as $user) 581 | { 582 | $this->assertFalse( $user->loaded(), "Ok, so you think the user was deleted? think again!"); 583 | } 584 | } 585 | } 586 | -------------------------------------------------------------------------------- /useradmin-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mixu/useradmin/458bedd41c73743395d9350c9cf766b862c55e81/useradmin-screen.png -------------------------------------------------------------------------------- /vendor/facebook/src/facebook.php: -------------------------------------------------------------------------------- 1 | constructSessionVariableName($key); 62 | $_SESSION[$session_var_name] = $value; 63 | } 64 | 65 | protected function getPersistentData($key, $default = false) 66 | { 67 | if (!in_array($key, self::$kSupportedKeys)) 68 | { 69 | self::errorLog('Unsupported key passed to getPersistentData.'); 70 | return $default; 71 | } 72 | 73 | $session_var_name = $this->constructSessionVariableName($key); 74 | return isset($_SESSION[$session_var_name]) ? 75 | $_SESSION[$session_var_name] : $default; 76 | } 77 | 78 | protected function clearPersistentData($key) 79 | { 80 | if (!in_array($key, self::$kSupportedKeys)) 81 | { 82 | self::errorLog('Unsupported key passed to clearPersistentData.'); 83 | return; 84 | } 85 | 86 | $session_var_name = $this->constructSessionVariableName($key); 87 | unset($_SESSION[$session_var_name]); 88 | } 89 | 90 | protected function clearAllPersistentData() 91 | { 92 | foreach (self::$kSupportedKeys as $key) 93 | { 94 | $this->clearPersistentData($key); 95 | } 96 | } 97 | 98 | protected function constructSessionVariableName($key) 99 | { 100 | return implode('_', array('fb', 101 | $this->getAppId(), 102 | $key)); 103 | } 104 | 105 | } -------------------------------------------------------------------------------- /vendor/facebook/src/fb_ca_chain_bundle.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFgjCCBGqgAwIBAgIQDKKbZcnESGaLDuEaVk6fQjANBgkqhkiG9w0BAQUFADBm 3 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 4 | d3cuZGlnaWNlcnQuY29tMSUwIwYDVQQDExxEaWdpQ2VydCBIaWdoIEFzc3VyYW5j 5 | ZSBDQS0zMB4XDTEwMDExMzAwMDAwMFoXDTEzMDQxMTIzNTk1OVowaDELMAkGA1UE 6 | BhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVBhbG8gQWx0bzEX 7 | MBUGA1UEChMORmFjZWJvb2ssIEluYy4xFzAVBgNVBAMUDiouZmFjZWJvb2suY29t 8 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC9rzj7QIuLM3sdHu1HcI1VcR3g 9 | b5FExKNV646agxSle1aQ/sJev1mh/u91ynwqd2BQmM0brZ1Hc3QrfYyAaiGGgEkp 10 | xbhezyfeYhAyO0TKAYxPnm2cTjB5HICzk6xEIwFbA7SBJ2fSyW1CFhYZyo3tIBjj 11 | 19VjKyBfpRaPkzLmRwIDAQABo4ICrDCCAqgwHwYDVR0jBBgwFoAUUOpzidsp+xCP 12 | nuUBINTeeZlIg/cwHQYDVR0OBBYEFPp+tsFBozkjrHlEnZ9J4cFj2eM0MA4GA1Ud 13 | DwEB/wQEAwIFoDAMBgNVHRMBAf8EAjAAMF8GA1UdHwRYMFYwKaAnoCWGI2h0dHA6 14 | Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9jYTMtZmIuY3JsMCmgJ6AlhiNodHRwOi8vY3Js 15 | NC5kaWdpY2VydC5jb20vY2EzLWZiLmNybDCCAcYGA1UdIASCAb0wggG5MIIBtQYL 16 | YIZIAYb9bAEDAAEwggGkMDoGCCsGAQUFBwIBFi5odHRwOi8vd3d3LmRpZ2ljZXJ0 17 | LmNvbS9zc2wtY3BzLXJlcG9zaXRvcnkuaHRtMIIBZAYIKwYBBQUHAgIwggFWHoIB 18 | UgBBAG4AeQAgAHUAcwBlACAAbwBmACAAdABoAGkAcwAgAEMAZQByAHQAaQBmAGkA 19 | YwBhAHQAZQAgAGMAbwBuAHMAdABpAHQAdQB0AGUAcwAgAGEAYwBjAGUAcAB0AGEA 20 | bgBjAGUAIABvAGYAIAB0AGgAZQAgAEQAaQBnAGkAQwBlAHIAdAAgAEMAUAAvAEMA 21 | UABTACAAYQBuAGQAIAB0AGgAZQAgAFIAZQBsAHkAaQBuAGcAIABQAGEAcgB0AHkA 22 | IABBAGcAcgBlAGUAbQBlAG4AdAAgAHcAaABpAGMAaAAgAGwAaQBtAGkAdAAgAGwA 23 | aQBhAGIAaQBsAGkAdAB5ACAAYQBuAGQAIABhAHIAZQAgAGkAbgBjAG8AcgBwAG8A 24 | cgBhAHQAZQBkACAAaABlAHIAZQBpAG4AIABiAHkAIAByAGUAZgBlAHIAZQBuAGMA 25 | ZQAuMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjANBgkqhkiG9w0BAQUF 26 | AAOCAQEACOkTIdxMy11+CKrbGNLBSg5xHaTvu/v1wbyn3dO/mf68pPfJnX6ShPYy 27 | 4XM4Vk0x4uaFaU4wAGke+nCKGi5dyg0Esg7nemLNKEJaFAJZ9enxZm334lSCeARy 28 | wlDtxULGOFRyGIZZPmbV2eNq5xdU/g3IuBEhL722mTpAye9FU/J8Wsnw54/gANyO 29 | Gzkewigua8ip8Lbs9Cht399yAfbfhUP1DrAm/xEcnHrzPr3cdCtOyJaM6SRPpRqH 30 | ITK5Nc06tat9lXVosSinT3KqydzxBYua9gCFFiR3x3DgZfvXkC6KDdUlDrNcJUub 31 | a1BHnLLP4mxTHL6faAXYd05IxNn/IA== 32 | -----END CERTIFICATE----- 33 | -----BEGIN CERTIFICATE----- 34 | MIIGVTCCBT2gAwIBAgIQCFH5WYFBRcq94CTiEsnCDjANBgkqhkiG9w0BAQUFADBs 35 | MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 36 | d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j 37 | ZSBFViBSb290IENBMB4XDTA3MDQwMzAwMDAwMFoXDTIyMDQwMzAwMDAwMFowZjEL 38 | MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 39 | LmRpZ2ljZXJ0LmNvbTElMCMGA1UEAxMcRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug 40 | Q0EtMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9hCikQH17+NDdR 41 | CPge+yLtYb4LDXBMUGMmdRW5QYiXtvCgFbsIYOBC6AUpEIc2iihlqO8xB3RtNpcv 42 | KEZmBMcqeSZ6mdWOw21PoF6tvD2Rwll7XjZswFPPAAgyPhBkWBATaccM7pxCUQD5 43 | BUTuJM56H+2MEb0SqPMV9Bx6MWkBG6fmXcCabH4JnudSREoQOiPkm7YDr6ictFuf 44 | 1EutkozOtREqqjcYjbTCuNhcBoz4/yO9NV7UfD5+gw6RlgWYw7If48hl66l7XaAs 45 | zPw82W3tzPpLQ4zJ1LilYRyyQLYoEt+5+F/+07LJ7z20Hkt8HEyZNp496+ynaF4d 46 | 32duXvsCAwEAAaOCAvcwggLzMA4GA1UdDwEB/wQEAwIBhjCCAcYGA1UdIASCAb0w 47 | ggG5MIIBtQYLYIZIAYb9bAEDAAIwggGkMDoGCCsGAQUFBwIBFi5odHRwOi8vd3d3 48 | LmRpZ2ljZXJ0LmNvbS9zc2wtY3BzLXJlcG9zaXRvcnkuaHRtMIIBZAYIKwYBBQUH 49 | AgIwggFWHoIBUgBBAG4AeQAgAHUAcwBlACAAbwBmACAAdABoAGkAcwAgAEMAZQBy 50 | AHQAaQBmAGkAYwBhAHQAZQAgAGMAbwBuAHMAdABpAHQAdQB0AGUAcwAgAGEAYwBj 51 | AGUAcAB0AGEAbgBjAGUAIABvAGYAIAB0AGgAZQAgAEQAaQBnAGkAQwBlAHIAdAAg 52 | AEMAUAAvAEMAUABTACAAYQBuAGQAIAB0AGgAZQAgAFIAZQBsAHkAaQBuAGcAIABQ 53 | AGEAcgB0AHkAIABBAGcAcgBlAGUAbQBlAG4AdAAgAHcAaABpAGMAaAAgAGwAaQBt 54 | AGkAdAAgAGwAaQBhAGIAaQBsAGkAdAB5ACAAYQBuAGQAIABhAHIAZQAgAGkAbgBj 55 | AG8AcgBwAG8AcgBhAHQAZQBkACAAaABlAHIAZQBpAG4AIABiAHkAIAByAGUAZgBl 56 | AHIAZQBuAGMAZQAuMA8GA1UdEwEB/wQFMAMBAf8wNAYIKwYBBQUHAQEEKDAmMCQG 57 | CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wgY8GA1UdHwSBhzCB 58 | hDBAoD6gPIY6aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0SGlnaEFz 59 | c3VyYW5jZUVWUm9vdENBLmNybDBAoD6gPIY6aHR0cDovL2NybDQuZGlnaWNlcnQu 60 | Y29tL0RpZ2lDZXJ0SGlnaEFzc3VyYW5jZUVWUm9vdENBLmNybDAfBgNVHSMEGDAW 61 | gBSxPsNpA/i/RwHUmCYaCALvY2QrwzAdBgNVHQ4EFgQUUOpzidsp+xCPnuUBINTe 62 | eZlIg/cwDQYJKoZIhvcNAQEFBQADggEBAF1PhPGoiNOjsrycbeUpSXfh59bcqdg1 63 | rslx3OXb3J0kIZCmz7cBHJvUV5eR13UWpRLXuT0uiT05aYrWNTf58SHEW0CtWakv 64 | XzoAKUMncQPkvTAyVab+hA4LmzgZLEN8rEO/dTHlIxxFVbdpCJG1z9fVsV7un5Tk 65 | 1nq5GMO41lJjHBC6iy9tXcwFOPRWBW3vnuzoYTYMFEuFFFoMg08iXFnLjIpx2vrF 66 | EIRYzwfu45DC9fkpx1ojcflZtGQriLCnNseaIGHr+k61rmsb5OPs4tk8QUmoIKRU 67 | 9ZKNu8BVIASm2LAXFszj0Mi0PeXZhMbT9m5teMl5Q+h6N/9cNUm/ocU= 68 | -----END CERTIFICATE----- 69 | -----BEGIN CERTIFICATE----- 70 | MIIEQjCCA6ugAwIBAgIEQoclDjANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC 71 | VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u 72 | ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc 73 | KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u 74 | ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEy 75 | MjIxNTI3MjdaFw0xNDA3MjIxNTU3MjdaMGwxCzAJBgNVBAYTAlVTMRUwEwYDVQQK 76 | EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xKzApBgNV 77 | BAMTIkRpZ2lDZXJ0IEhpZ2ggQXNzdXJhbmNlIEVWIFJvb3QgQ0EwggEiMA0GCSqG 78 | SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGzOVz5vvUu+UtLTKm3+WBP8nNJUm2cSrD 79 | 1ZQ0Z6IKHLBfaaZAscS3so/QmKSpQVk609yU1jzbdDikSsxNJYL3SqVTEjju80lt 80 | cZF+Y7arpl/DpIT4T2JRvvjF7Ns4kuMG5QiRDMQoQVX7y1qJFX5x6DW/TXIJPb46 81 | OFBbdzEbjbPHJEWap6xtABRaBLe6E+tRCphBQSJOZWGHgUFQpnlcid4ZSlfVLuZd 82 | HFMsfpjNGgYWpGhz0DQEE1yhcdNafFXbXmThN4cwVgTlEbQpgBLxeTmIogIRfCdm 83 | t4i3ePLKCqg4qwpkwr9mXZWEwaElHoddGlALIBLMQbtuC1E4uEvLAgMBAAGjggET 84 | MIIBDzASBgNVHRMBAf8ECDAGAQH/AgEBMCcGA1UdJQQgMB4GCCsGAQUFBwMBBggr 85 | BgEFBQcDAgYIKwYBBQUHAwQwMwYIKwYBBQUHAQEEJzAlMCMGCCsGAQUFBzABhhdo 86 | dHRwOi8vb2NzcC5lbnRydXN0Lm5ldDAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8v 87 | Y3JsLmVudHJ1c3QubmV0L3NlcnZlcjEuY3JsMB0GA1UdDgQWBBSxPsNpA/i/RwHU 88 | mCYaCALvY2QrwzALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8BdiE1U9s/8KAGv7 89 | UISX8+1i0BowGQYJKoZIhvZ9B0EABAwwChsEVjcuMQMCAIEwDQYJKoZIhvcNAQEF 90 | BQADgYEAUuVY7HCc/9EvhaYzC1rAIo348LtGIiMduEl5Xa24G8tmJnDioD2GU06r 91 | 1kjLX/ktCdpdBgXadbjtdrZXTP59uN0AXlsdaTiFufsqVLPvkp5yMnqnuI3E2o6p 92 | NpAkoQSbB6kUCNnXcW26valgOjDLZFOnr241QiwdBAJAAE/rRa8= 93 | -----END CERTIFICATE----- 94 | -----BEGIN CERTIFICATE----- 95 | MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC 96 | VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u 97 | ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc 98 | KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u 99 | ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1 100 | MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE 101 | ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j 102 | b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF 103 | bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg 104 | U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA 105 | A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/ 106 | I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3 107 | wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC 108 | AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb 109 | oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5 110 | BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p 111 | dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk 112 | MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp 113 | b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu 114 | dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0 115 | MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi 116 | E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa 117 | MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI 118 | hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN 119 | 95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd 120 | 2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI= 121 | -----END CERTIFICATE----- 122 | -------------------------------------------------------------------------------- /vendor/recaptcha/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2007 reCAPTCHA -- http://recaptcha.net 2 | AUTHORS: 3 | Mike Crawford 4 | Ben Maurer 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /vendor/recaptcha/README: -------------------------------------------------------------------------------- 1 | reCAPTCHA README 2 | ================ 3 | 4 | The reCAPTCHA PHP Lirary helps you use the reCAPTCHA API. Documentation 5 | for this library can be found at 6 | 7 | http://recaptcha.net/plugins/php 8 | -------------------------------------------------------------------------------- /vendor/recaptcha/example-captcha.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
    4 | is_valid) { 25 | echo "You got it!"; 26 | } else { 27 | # set the error code so that we can display it 28 | $error = $resp->error; 29 | } 30 | } 31 | echo recaptcha_get_html($publickey, $error); 32 | ?> 33 |
    34 | 35 |
    36 | 37 | 38 | -------------------------------------------------------------------------------- /vendor/recaptcha/example-mailhide.php: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | The Mailhide version of example@example.com is 12 | .
    13 | 14 | The url for the email is: 15 |
    16 | 17 | 18 | -------------------------------------------------------------------------------- /vendor/recaptcha/recaptchalib.php: -------------------------------------------------------------------------------- 1 | $value ) 50 | $req .= $key . '=' . urlencode( stripslashes($value) ) . '&'; 51 | 52 | // Cut the last '&' 53 | $req=substr($req,0,strlen($req)-1); 54 | return $req; 55 | } 56 | 57 | 58 | 59 | /** 60 | * Submits an HTTP POST to a reCAPTCHA server 61 | * @param string $host 62 | * @param string $path 63 | * @param array $data 64 | * @param int port 65 | * @return array response 66 | */ 67 | function _recaptcha_http_post($host, $path, $data, $port = 80) { 68 | 69 | $req = _recaptcha_qsencode ($data); 70 | 71 | $http_request = "POST $path HTTP/1.0\r\n"; 72 | $http_request .= "Host: $host\r\n"; 73 | $http_request .= "Content-Type: application/x-www-form-urlencoded;\r\n"; 74 | $http_request .= "Content-Length: " . strlen($req) . "\r\n"; 75 | $http_request .= "User-Agent: reCAPTCHA/PHP\r\n"; 76 | $http_request .= "\r\n"; 77 | $http_request .= $req; 78 | 79 | $response = ''; 80 | if( false == ( $fs = @fsockopen($host, $port, $errno, $errstr, 10) ) ) { 81 | die ('Could not open socket'); 82 | } 83 | 84 | fwrite($fs, $http_request); 85 | 86 | while ( !feof($fs) ) 87 | $response .= fgets($fs, 1160); // One TCP-IP packet 88 | fclose($fs); 89 | $response = explode("\r\n\r\n", $response, 2); 90 | 91 | return $response; 92 | } 93 | 94 | 95 | 96 | /** 97 | * Gets the challenge HTML (javascript and non-javascript version). 98 | * This is called from the browser, and the resulting reCAPTCHA HTML widget 99 | * is embedded within the HTML form it was called from. 100 | * @param string $pubkey A public key for reCAPTCHA 101 | * @param string $error The error given by reCAPTCHA (optional, default is null) 102 | * @param boolean $use_ssl Should the request be made over ssl? (optional, default is false) 103 | 104 | * @return string - The HTML to be embedded in the user's form. 105 | */ 106 | function recaptcha_get_html ($pubkey, $error = null, $use_ssl = false) 107 | { 108 | if ($pubkey == null || $pubkey == '') { 109 | die ("To use reCAPTCHA you must get an API key from https://www.google.com/recaptcha/admin/create"); 110 | } 111 | 112 | if ($use_ssl) { 113 | $server = RECAPTCHA_API_SECURE_SERVER; 114 | } else { 115 | $server = RECAPTCHA_API_SERVER; 116 | } 117 | 118 | $errorpart = ""; 119 | if ($error) { 120 | $errorpart = "&error=" . $error; 121 | } 122 | return ' 123 | 124 | '; 129 | } 130 | 131 | 132 | 133 | 134 | /** 135 | * A ReCaptchaResponse is returned from recaptcha_check_answer() 136 | */ 137 | class ReCaptchaResponse { 138 | var $is_valid; 139 | var $error; 140 | } 141 | 142 | 143 | /** 144 | * Calls an HTTP POST function to verify if the user's guess was correct 145 | * @param string $privkey 146 | * @param string $remoteip 147 | * @param string $challenge 148 | * @param string $response 149 | * @param array $extra_params an array of extra variables to post to the server 150 | * @return ReCaptchaResponse 151 | */ 152 | function recaptcha_check_answer ($privkey, $remoteip, $challenge, $response, $extra_params = array()) 153 | { 154 | if ($privkey == null || $privkey == '') { 155 | die ("To use reCAPTCHA you must get an API key from https://www.google.com/recaptcha/admin/create"); 156 | } 157 | 158 | if ($remoteip == null || $remoteip == '') { 159 | die ("For security reasons, you must pass the remote ip to reCAPTCHA"); 160 | } 161 | 162 | 163 | 164 | //discard spam submissions 165 | if ($challenge == null || strlen($challenge) == 0 || $response == null || strlen($response) == 0) { 166 | $recaptcha_response = new ReCaptchaResponse(); 167 | $recaptcha_response->is_valid = false; 168 | $recaptcha_response->error = 'incorrect-captcha-sol'; 169 | return $recaptcha_response; 170 | } 171 | 172 | $response = _recaptcha_http_post (RECAPTCHA_VERIFY_SERVER, "/recaptcha/api/verify", 173 | array ( 174 | 'privatekey' => $privkey, 175 | 'remoteip' => $remoteip, 176 | 'challenge' => $challenge, 177 | 'response' => $response 178 | ) + $extra_params 179 | ); 180 | 181 | $answers = explode ("\n", $response [1]); 182 | $recaptcha_response = new ReCaptchaResponse(); 183 | 184 | if (trim ($answers [0]) == 'true') { 185 | $recaptcha_response->is_valid = true; 186 | } 187 | else { 188 | $recaptcha_response->is_valid = false; 189 | $recaptcha_response->error = $answers [1]; 190 | } 191 | return $recaptcha_response; 192 | 193 | } 194 | 195 | /** 196 | * gets a URL where the user can sign up for reCAPTCHA. If your application 197 | * has a configuration page where you enter a key, you should provide a link 198 | * using this function. 199 | * @param string $domain The domain where the page is hosted 200 | * @param string $appname The name of your application 201 | */ 202 | function recaptcha_get_signup_url ($domain = null, $appname = null) { 203 | return "https://www.google.com/recaptcha/admin/create?" . _recaptcha_qsencode (array ('domains' => $domain, 'app' => $appname)); 204 | } 205 | 206 | function _recaptcha_aes_pad($val) { 207 | $block_size = 16; 208 | $numpad = $block_size - (strlen ($val) % $block_size); 209 | return str_pad($val, strlen ($val) + $numpad, chr($numpad)); 210 | } 211 | 212 | /* Mailhide related code */ 213 | 214 | function _recaptcha_aes_encrypt($val,$ky) { 215 | if (! function_exists ("mcrypt_encrypt")) { 216 | die ("To use reCAPTCHA Mailhide, you need to have the mcrypt php module installed."); 217 | } 218 | $mode=MCRYPT_MODE_CBC; 219 | $enc=MCRYPT_RIJNDAEL_128; 220 | $val=_recaptcha_aes_pad($val); 221 | return mcrypt_encrypt($enc, $ky, $val, $mode, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"); 222 | } 223 | 224 | 225 | function _recaptcha_mailhide_urlbase64 ($x) { 226 | return strtr(base64_encode ($x), '+/', '-_'); 227 | } 228 | 229 | /* gets the reCAPTCHA Mailhide url for a given email, public key and private key */ 230 | function recaptcha_mailhide_url($pubkey, $privkey, $email) { 231 | if ($pubkey == '' || $pubkey == null || $privkey == "" || $privkey == null) { 232 | die ("To use reCAPTCHA Mailhide, you have to sign up for a public and private key, " . 233 | "you can do so at http://www.google.com/recaptcha/mailhide/apikey"); 234 | } 235 | 236 | 237 | $ky = pack('H*', $privkey); 238 | $cryptmail = _recaptcha_aes_encrypt ($email, $ky); 239 | 240 | return "http://www.google.com/recaptcha/mailhide/d?k=" . $pubkey . "&c=" . _recaptcha_mailhide_urlbase64 ($cryptmail); 241 | } 242 | 243 | /** 244 | * gets the parts of the email to expose to the user. 245 | * eg, given johndoe@example,com return ["john", "example.com"]. 246 | * the email is then displayed as john...@example.com 247 | */ 248 | function _recaptcha_mailhide_email_parts ($email) { 249 | $arr = preg_split("/@/", $email ); 250 | 251 | if (strlen ($arr[0]) <= 4) { 252 | $arr[0] = substr ($arr[0], 0, 1); 253 | } else if (strlen ($arr[0]) <= 6) { 254 | $arr[0] = substr ($arr[0], 0, 3); 255 | } else { 256 | $arr[0] = substr ($arr[0], 0, 4); 257 | } 258 | return $arr; 259 | } 260 | 261 | /** 262 | * Gets html to display an email address given a public an private key. 263 | * to get a key, go to: 264 | * 265 | * http://www.google.com/recaptcha/mailhide/apikey 266 | */ 267 | function recaptcha_mailhide_html($pubkey, $privkey, $email) { 268 | $emailparts = _recaptcha_mailhide_email_parts ($email); 269 | $url = recaptcha_mailhide_url ($pubkey, $privkey, $email); 270 | 271 | return htmlentities($emailparts[0]) . "...@" . htmlentities ($emailparts [1]); 273 | 274 | } 275 | 276 | 277 | ?> 278 | -------------------------------------------------------------------------------- /views/pagination/useradmin.php: -------------------------------------------------------------------------------- 1 | 10 | 48 | -------------------------------------------------------------------------------- /views/template/default.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <?php echo $title ?> 5 | 6 | $type) echo HTML::style($file, array('media' => $type)), "\n" ?> 7 | 8 | 9 | 10 | 11 |
    12 | 13 | 30 |
    31 | 35 |
    36 |
    37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /views/template/useradmin.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <?php echo $title ?> 5 | 6 | $type) echo HTML::style($file, array('media' => $type)), "\n" ?> 7 | 8 | 9 | 10 | 11 |
    12 | 13 | 30 |
    31 | 0) { 34 | echo '
    '; 35 | echo '
    '; 36 | echo Message::output(); 37 | echo '
    '; 38 | } 39 | echo $content ?> 40 |
    41 |
    42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /views/user/admin/delete.php: -------------------------------------------------------------------------------- 1 |
    2 |

    Delete user?

    3 |
    4 | 'display: inline;')); 7 | 8 | echo Form::hidden('id', $id); 9 | 10 | echo '

    '.__('Are you sure you want to delete user ":user"', array(':user' => $data['username'])).'

    '; 11 | 12 | echo '

    '.Form::radio('confirmation', 'Y', false, array('id' => 'conf_y')).'
    '; 13 | echo Form::radio('confirmation', 'N', true, array('id' => 'conf_n')).'

    '; 14 | echo Form::submit(NULL, __('Delete')); 15 | echo Form::close(); 16 | 17 | echo Form::open('admin_user/index', array('style' => 'display: inline; padding-left: 10px;')); 18 | echo Form::submit(NULL, __('Cancel')); 19 | echo Form::close(); 20 | ?> 21 |
    22 |
    23 | 24 | -------------------------------------------------------------------------------- /views/user/admin/edit.php: -------------------------------------------------------------------------------- 1 | errors = $errors; 6 | } 7 | if(isset($data)) { 8 | unset($data['password']); 9 | unset($data['password_confirm']); 10 | $form->values = $data; 11 | } 12 | echo $form->open('admin_user/edit/'.$id); 13 | ?> 14 | hidden('id', $id); 16 | ?> 17 |
    18 |

    19 |
    20 |
      21 |
    • 22 | input('username', null, array('info' => __('Length between 1-32 characters. Letters, numbers, dot and underscore are allowed characters.'))); ?> 23 |
    • 24 | input('email') ?> 25 |
    • 26 | password('password', null, array('info' => __('Password should be between 6-42 characters.'))) ?> 27 |
    • 28 | password('password_confirm') ?> 29 |
    • 30 |
    • 31 | 32 | $description) { 35 | echo ''; 40 | echo ''; 41 | echo ''; 42 | echo ''; 43 | $i++; 44 | } 45 | ?> 46 |
      '.Form::checkbox('roles['.$role.']', $role, (in_array($role, $user_roles) ? true : false)).''.ucfirst($role).''.$description.'
      47 |
    • 48 |
    49 |
    50 | submit(NULL, __('Save')); 52 | echo $form->close(); 53 | ?> 54 |
    55 |
    56 | -------------------------------------------------------------------------------- /views/user/admin/index.php: -------------------------------------------------------------------------------- 1 | 8 |
    9 | 15 |

    16 |
    17 | render(); 19 | // format data for DataTable 20 | $data = array(); 21 | $merge = null; 22 | $providers = array_filter(Kohana::$config->load('useradmin.providers')); 23 | foreach ($users as $user) { 24 | $row = $user->as_array(); 25 | // reformat dates 26 | $row['created'] = Helper_Format::friendly_datetime($row['created']); 27 | $row['modified'] = Helper_Format::friendly_datetime($row['modified']); 28 | $row['last_login'] = Helper_Format::relative_time($row['last_login']); 29 | // $row['last_failed_login'] = Helper_Format::relative_time(strtotime($row['last_failed_login'])); 30 | // add actions 31 | $row['actions'] = Html::anchor('admin_user/edit/'.$row['id'], __('Edit')).' | '.Html::anchor('admin_user/delete/'.$row['id'], __('Delete')); 32 | // set roles 33 | $row['role'] = ''; 34 | foreach($user->roles->where('name', '!=', 'login')->find_all() as $role) { 35 | $row['role'] .= $role->name.', '; 36 | } 37 | // remove last comma 38 | $row['role'] = substr($row['role'], 0, -2); 39 | // add provider icons 40 | if(!empty($providers)) { 41 | $row['identities'] = ''; 42 | $identities = $user->user_identity->find_all(); 43 | if($identities->count() > 0) { 44 | foreach($identities as $identity) { 45 | $row['identities'] .= ' '; 46 | } 47 | } 48 | } 49 | $data[] = $row; 50 | } 51 | 52 | $column_list = array( 'username' => array( 'label' => __('Username') ), 53 | 'role' => array( 'label' => __('Role(s)'), 'sortable' => false ), 54 | 'last_login' => array( 'label' => __('Last login') ), 55 | 'logins' => array( 'label' => __('# of logins') ), 56 | ); 57 | if(!empty($providers)) { 58 | $column_list['identities'] = array( 'label' => __('Identities'), 'sortable' => false ); 59 | } 60 | $column_list['actions'] = array( 'label' => __('Actions'), 'sortable' => false ); 61 | $datatable = new Helper_Datatable($column_list, array('paginator' => true, 'class' => 'table', 'sortable' => 'true', 'default_sort' => 'username')); 62 | $datatable->values($data); 63 | echo $datatable->render(); 64 | echo $paging->render(); 65 | ?> 66 |
    67 |
    68 | -------------------------------------------------------------------------------- /views/user/associate.php: -------------------------------------------------------------------------------- 1 |
    2 |

    Confirm associating your user account

    3 |
    4 | 'display: inline;')); 7 | 8 | echo '

    You are about to associate your user account with your '.ucfirst($provider_name).' account. After this, you can log in using that account. Are you sure?

    '; 9 | 10 | echo Form::hidden('confirmation', 'Y'); 11 | 12 | echo Form::submit(NULL, 'Yes'); 13 | echo Form::close(); 14 | 15 | echo Form::open('user/profile', array('style' => 'display: inline; padding-left: 10px;')); 16 | echo Form::submit(NULL, 'Cancel'); 17 | echo Form::close(); 18 | ?> 19 |
    20 |
    -------------------------------------------------------------------------------- /views/user/login.php: -------------------------------------------------------------------------------- 1 | errors = $errors; 5 | } 6 | if(isset($username)) { 7 | $form->values['username'] = $username; 8 | } 9 | // set custom classes to get labels moved to bottom: 10 | $form->error_class = 'error block'; 11 | $form->info_class = 'info block'; 12 | 13 | ?> 14 |
    15 |
    16 |

    17 |
    18 | open('user/login'); 20 | echo '
    '; 21 | echo '
      '; 22 | 23 | echo '
    • '.$form->label('username', __('Email or Username')).'
    • '; 24 | echo $form->input('username', null, array('class' => 'text twothirds')); 25 | 26 | echo '
    • '.$form->label('password', __('Password'),array('style'=>'display: inline; margin-right:10px;')). 27 | ' '.Html::anchor('user/forgot', __('Forgot your password?')).'
      '. 28 | '
    • '; 29 | echo $form->password('password', null, array('class' => 'text twothirds')); 30 | 31 | 32 | $authClass = new ReflectionClass(get_class(Auth::instance())); 33 | if($authClass->hasMethod('auto_login')) 34 | { 35 | echo '
    • '.Kohana_Form::checkbox('remember','remember',false,array('style'=>'margin-right: 10px','id'=>'remember')). 36 | $form->label('remember', __('Remember me'),array('style'=>'display: inline')). 37 | $form->submit(NULL, __('Login'),array('style'=>'float: right;')). 38 | '
    • '; 39 | echo '
    '; 40 | } 41 | else 42 | { 43 | echo ''; 44 | echo $form->submit(NULL, __('Login')); 45 | } 46 | echo $form->close(); 47 | echo '
     '; 48 | 49 | echo '
      '; 50 | echo '
    • '.__('Don\'t have an account?').' '.Html::anchor('user/register', __('Register a new account')).'.
    • '; 51 | $options = array_filter(Kohana::$config->load('useradmin.providers')); 52 | if(!empty($options)) { 53 | echo '
    • '; 54 | echo '
    • '; 55 | if(isset($options['facebook']) && $options['facebook']) { 56 | echo ''; 57 | } 58 | if(isset($options['twitter']) && $options['twitter']) { 59 | echo ''; 60 | } 61 | if(isset($options['google']) && $options['google']) { 62 | echo ''; 63 | } 64 | if(isset($options['yahoo']) && $options['yahoo']) { 65 | echo ''; 66 | } 67 | echo '
      68 |
    • '; 69 | } 70 | echo '
    '; 71 | echo '
    '; 72 | ?> 73 |
    74 |
    75 |
    76 | 77 | 78 | -------------------------------------------------------------------------------- /views/user/noaccess.php: -------------------------------------------------------------------------------- 1 |
    2 |

    3 |
    4 |

    5 |
    6 |
    7 | 8 | -------------------------------------------------------------------------------- /views/user/profile.php: -------------------------------------------------------------------------------- 1 | 2 |
    3 | 10 |

    11 |
    12 |

    This is your user information, username ?>.

    13 | 14 |

    Username & Email Address

    15 |

    username ?> — email ?>

    16 | 17 |

    Login Activity

    18 |

    Last login was last_login) ?>, at last_login) ?>.
    Total logins: logins ?>

    19 | 20 | load('useradmin.providers')); 22 | $identities = $user->user_identity->find_all(); 23 | if($identities->count() > 0) { 24 | echo '

    Accounts associated with your user profile

    '; 25 | foreach($identities as $identity) { 26 | echo ''; 27 | unset($providers[$identity->provider]); 28 | } 29 | echo '

    '; 30 | } 31 | if(!empty($providers)) { 32 | echo '

    Additional account providers

    Click the provider icon to associate it with your existing account.

    '; 33 | foreach($providers as $provider => $enabled) { 34 | echo ''; 35 | } 36 | echo '

    '; 37 | } 38 | ?> 39 |
    40 |
    41 | -------------------------------------------------------------------------------- /views/user/profile_edit.php: -------------------------------------------------------------------------------- 1 | errors = $errors; 5 | } 6 | if(isset($data)) { 7 | unset($data['password']); 8 | $form->values = $data; 9 | } 10 | echo $form->open('user/profile_edit'); 11 | ?> 12 |
    13 |

    14 |
    15 |
      16 |
    • 17 | input('username', null, array('info' => __('Length between 4-32 characters. Letters, numbers, dot and underscore are allowed characters.'))); ?> 18 |
    • 19 | input('email') ?> 20 |
    • 21 | password('password', null, array('info' => __('Password should be between 6-42 characters.'))) ?> 22 |
    • 23 | password('password_confirm') ?> 24 |
    • 25 |
    • 26 | 27 | $description) { 30 | echo ''; 35 | echo ''; 36 | echo ''; 37 | $i++; 38 | } 39 | ?> 40 |
      '.ucfirst($role).''.$description.'
      41 |
    • 42 |
    43 |
    44 | submit(NULL, __('Save')); 46 | echo $form->close(); 47 | ?> 48 |
    49 |
    -------------------------------------------------------------------------------- /views/user/register.php: -------------------------------------------------------------------------------- 1 | 2 |
    3 |

    4 |
    5 |
    6 |
    7 |

    8 | 9 | errors = $errors; 13 | } 14 | if(isset($defaults)) { 15 | $form->defaults = $defaults; 16 | } else { 17 | unset($_POST['password']); 18 | unset($_POST['password_confirmation']); 19 | $form->defaults = $_POST; 20 | } 21 | echo $form->open('user/register'); 22 | ?> 23 | 24 |
      25 |
    • 26 | input('username', null, array('info' => __('Length between 1-32 characters. Letters, numbers, dot and underscore are allowed characters.'))); ?> 27 |
    • 28 | input('email') ?> 29 |
    • 30 | password('password', null, array('info' => __('Password should be between 6-42 characters.'))) ?> 31 |
    • 32 | password('password_confirm') ?> 33 | 34 |
    • 35 | 36 |
      37 |
    • 38 | 39 |
    • submit(NULL, __('Register new account')); ?>
    • 40 |
    41 |
    42 | close(); 44 | ?> 45 |
    46 |
    -------------------------------------------------------------------------------- /views/user/reset/forgot.php: -------------------------------------------------------------------------------- 1 | 2 |
    3 |

    4 |
    5 |

    6 | '.__('Your email address:').' '.Form::input('reset_email', '', array('class' => 'text')).'

    '; 9 | ?> 10 | 11 | 15 |
    16 |
    17 | -------------------------------------------------------------------------------- /views/user/reset/reset.php: -------------------------------------------------------------------------------- 1 |
    2 |

    3 |
    4 | 7 |
      8 |
    • 9 | 10 | 'text')) ?> 11 |
    • 12 |
    • 13 | 14 | 'text')) ?> 15 |
    • 16 |
    17 |
    18 | 19 | 20 | 21 | 22 |
    23 |
    24 | -------------------------------------------------------------------------------- /views/user/unregister.php: -------------------------------------------------------------------------------- 1 |
    2 |

    Confirm removing your user account

    3 |
    4 | 'display: inline;')); 7 | 8 | echo Form::hidden('id', $id); 9 | 10 | echo '

    Are you sure you want to remove your user account?

    '; 11 | 12 | echo '

    '.Form::radio('confirmation', 'Y').' Yes
    '; 13 | echo Form::radio('confirmation', 'N', true).' No

    '; 14 | 15 | echo Form::submit(NULL, 'Confirm'); 16 | echo Form::close(); 17 | 18 | echo Form::open('user/profile', array('style' => 'display: inline; padding-left: 10px;')); 19 | echo Form::submit(NULL, 'Cancel'); 20 | echo Form::close(); 21 | ?> 22 |
    23 |
    --------------------------------------------------------------------------------