Subversion Repositories php_users

Rev

Rev 27 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
10 rodolico 1
====== PHP Users Class ======
2
 
3
I got frustrated trying to find a class or library to authenticate user logins in PHP. The ones I found were either too simplistic, or required me to "join" some project just to have access to purportedly open source software.
4
 
23 rodolico 5
So, I decided to dust off the neurons and see if I could build one. I decided to make it as flexible as possible, with only the very basics, but able to be enhanced via data calls. I also decided to make the data access independent of the class itself so data access classes could be written for tasks other than MySQL using the mysqli library.
10 rodolico 6
 
23 rodolico 7
Because of this, usersDataSource is an abstract class which can not be instantiated. Instead, you must extend the class, defining all of the abstract methods in the abstract. We've done this with the UsersDataSourceMySQLi class.
8
 
6 rodolico 9
By itself, the users class (with a data access class usersDataSource like the included UsersDataSourceMySQLi class) handles basic login/logout/editing functions.
4 rodolico 10
 
11
The class(es) were, however, designed for extensibility and customization in mind. Some design considerations were made with this in mind.
12
 
10 rodolico 13
NOTE: This code uses the ternary and null coelescing shortcuts ?: and ??. I THINK these were introduced in PHP 5.3, but not sure. This code will not work on versions which do not have these shortcuts. See https://www.php.net/manual/en/language.operators.comparison.php
4 rodolico 14
 
6 rodolico 15
You can get a copy of this from our subversion repository
10 rodolico 16
<code bash>
16 rodolico 17
svn co http://svn.dailydata.net/svn/php_users/tags/stable php_users
10 rodolico 18
</code>
6 rodolico 19
My working copy is at
20
http://svn.dailydata.net/svn/php_users/trunk
21
but I recommend NOT using that as I use trunk as my personal playground and will commit broken code to it regularly
22
 
28 rodolico 23
An extension of this basic class which adds boolean permissions is the [[software:dailydata:libraries:php_user_permissions|UsersPermissions]] class. It is part of the same library.
24
 
10 rodolico 25
==== Basic System ====
4 rodolico 26
 
27
With no modification, the system will store username, password and two booleans, isAdmin and enabled. The default table is created as
28
 
29
<code sql>
30
create or replace table _users (
31
   _user_id    int unsigned not null auto_increment,
22 rodolico 32
   login       varchar(64), /* the login name */
33
   password    varchar(128), /* encrypted form of the password */
34
   isAdmin     boolean DEFAULT 1, /* if true, user has full admin rights */
35
   enabled     boolean DEFAULT 1, /* if false, user can not log in */
4 rodolico 36
   primary key (_user_id)
37
);
38
</code>
39
 
10 rodolico 40
  * login is filtered to alpha-numerics and the underscore character
41
  * password is stored as a hash using PHP's password_hash
42
  * users with isAdmin set will be able to add/edit other users
43
  * users with enabled set to false (0) will not be able to log in
4 rodolico 44
 
23 rodolico 45
NOTE: the usersDataSourceMySQLi class has a public function, buildTable, which will build the table, so installation involves simply calling that function if you are using that data source class.
4 rodolico 46
 
47
IMPORTANT: to allow the Users class to work with a wide variety of data types, it does no data access itself. It requires a data access class.
48
 
49
Basic use in a script involves instantiating a data access class object, then instantiating a Users class object.
50
 
51
Example:
52
 
53
<code php>
54
<?php
55
   include_once( 'UsersDataSourceMySQLi.class.php' );
56
   include_once( 'Users.class.php' );
57
   session_start();
28 rodolico 58
   $connection = new UsersDataSourceMySQLi( 
4 rodolico 59
         null,
60
         null, 
61
         array( 'username' => 'test', 'password' => 'test', 'database' => 'test' ) 
62
      );
63
   //$connection->buildTable( 'admin', 'admin' ); die;
64
   // ensure we always have a (possibly invalid) instance of user
65
   if ( ! isset( $_SESSION['user'] ) ) { 
66
      $_SESSION['user'] = new Users( );
67
   }
68
?>
69
<html>
70
   <body>
71
      <div class="login">
72
         <?php 
73
            if ( isset( $_SESSION['user'] ) )
74
               print $_SESSION['user']->HTML($connection); 
75
         ?>
76
      </div>
77
   </body>
78
</html>
79
</code>
80
 
28 rodolico 81
This example is using the UsersDataSourceMySQLi definition of  data access (included)
4 rodolico 82
 
83
If you run it the first time with <code php>$connection->buildTable( 'admin', 'admin' ); die;</code> uncommented, it will build the table. Comment that line out on the next run and you will be presented with a login screen.
84
 
85
Class function HTML() displays various things to allow login, then quits displaying anything. Setting $_REQUEST['logout'] = 1 before calling HTML() will initiate a log out which will destroy the session variable
86
 
10 rodolico 87
==== Full Functionality ====
4 rodolico 88
 
89
Calling the class method admin and displaying the repeatedly will generate output allowing the user to change their username, password and any other fields which do not have the 'restrict' attribute set to true
90
 
91
If the user has admin rights set, it will also display a list of logins and allow you to select one to edit or, add a new user. Editing someone else shows all fields, whether or not the 'restrict' attribute is set to true.
92
 
10 rodolico 93
==== CSS ====
4 rodolico 94
 
10 rodolico 95
I tried to not put any HTML layout into the code, relying instead on CSS (Thanks, Randell). Everything is supposed to have a class and be wrapped in a <div> with a class. Following are the classes I have in the code (I THINK).
4 rodolico 96
* login_field = This is the class of all INPUT fields, and div's surrounding them
97
* login_form  = the class for all <form definitions
98
* login_list  = the class of the unsigned list (ul) which displays the links to edit other users for admin's
99
* login       = NOT in code, but used in example as a div around the div around the login area
100
 
10 rodolico 101
==== Extended Functionality ====
4 rodolico 102
 
103
First, everything is pretty basic. I tried to limit the number of fields to the absolute minimum, but also set it up to allow additional fields to be added programmatically. The example does this.
104
 
105
If you open the class source, you'll find the private member $dbDefinition, which defines everything in the code (I hope). When you create a new instance, you can pass the constructor an array which will be merged with this member, optionally increasing the number and definition of the fields.
106
 
6 rodolico 107
WARNING: if you add new fields to the Users class, you must also add them to class usersDataSource. See below
4 rodolico 108
 
109
Let's add a new field, say we want to store the users e-mail address. In our PHP, create an array as follows:
110
 
111
<code php>
112
   $customFields = array( 
113
      'tables' => array(
114
         'users' => array(
115
            'fields' => array(
116
               'email' => array(
117
                     // == For Users class ==
118
                     // this will be the display label on the form
119
                     'label'  => 'E-Mail',
120
                     // the input type to use for data entry
121
                     'html type' => 'text',
122
                     // you can only edit this if an admin and changing someone
123
                     // else' record
124
                     'restrict' => false,
125
                     // will be displayed on a hover in HTML5 (ie, title=)
126
                     'instructions' => 'Enter your e-mail address (WARNING, not verified)',
127
                     // this is entered in an empty box, ie placeholder=
128
                     'hint'     => 'E-Mail Address',
129
                     // a regex to run it against to verify it is ok
11 rodolico 130
                     'filter'   => '/^[-_a-z0-9.]+@[_a-z0-9]+\.[a-z0-9]+$/i',
4 rodolico 131
 
132
                     // == for Data Source ==
133
                     'dbColumn'  =>  'email',
134
                     // actual mySQL column type
135
                     'type'      => 'varchar(64)',
136
                     // set it to not null if we build the table ourselves
137
                     'required'  => false
138
                     )
139
                  )
140
               )
141
            )
142
      );
143
 ?>
144
 </code>
145
 
28 rodolico 146
 Now, when we instantiate a new object of class Users AND class UsersDataSourceMySQLi, we simply pass this array in.
4 rodolico 147
 
148
 <code php>
28 rodolico 149
    $connection = new UsersDataSourceMySQLi( 
4 rodolico 150
         null,
151
         $customFields, 
152
         array( 'username' => 'test', 'password' => 'test', 'database' => 'test' ) 
153
      );
154
   if ( ! isset( $_SESSION['user'] ) ) { 
155
      $_SESSION['user'] = new Users( $customFields );
156
   }
10 rodolico 157
</code>
4 rodolico 158
 
6 rodolico 159
Note that since we replicated the basic structure of $dbDefinition in Users and usersDataSource, we can use the same hash to pass into both; they will store, but ignore values not relevant to them.
4 rodolico 160
 
161
When the usersDataSource and Users objects are created, $customFields will be merged, with duplicates overwritten by the $customFields value.
162
 
163
This is not limited to adding new columns; you can modify the display definitions also, ie how the information is stored on the screen, to some extent.
6 rodolico 164
 
10 rodolico 165
=== New Column Definitions ===
166
 
167
Note: If a new column is defined with the name (see below) of 'last password change', anytime a password is changed, it will be updated to the current date/time in YYYYMMDDHHMMSS format, which can be directly plugged into MySQL. The code is in there, but to not overpopulate the tables with columns that may not be necessary, I did not include this column in the default table structure.
168
 
169
The structure of a new column only requires a value for 
170
  * type
171
  * html type
172
  * dbColumn
173
Defaults (generally empty strings) will be used for anything else.
174
 
175
== key name ==
176
The key is used throughout the program to identify what column we are working on. It can be any value that can be used as a PHP hash key.
177
 
178
== html type (required) ==
179
This determines how the field is displayed and processed during editing. Accepted values are:
180
  * text - standard text input field, corresponds to SQL varchar or char field
181
  * textarea - multi-line strings of arbitrary lenght, corresponds to mysql text field
182
  * boolean - displayed as checkbox, corresponds to mysql bool and/or tinyint (ie, 0 and 1 only)
183
  * password - displayed as a type='password' field, not pre-populated or displayed when typing. Uses varchar(128) in mysql for the length of the hash created
184
Any value not in the list above will probably result in weird errors.
185
 
186
== dbColumn (required) ==
187
This is the column name in the database for this field, and all sql uses this value when accessing a column.
188
 
189
== type (required for creating tables) ==
190
this is a valid MySQL database type used by the buildTable function to create the table, ie varchar, char, 
191
 
192
== label ==
193
If set, will be the label for the input screens. If not defined, the label defaults to the key.
194
 
195
== instructions ==
196
Replaces the html TITLE attribute for an INPUT, which is displayed by default on a hover. If not set, defaults to an empty string.
197
 
198
== hint ==
199
Placed in the PLACEHODER attribute for an INPUT. Displayed in an empty text field most of the time. If not set, defaults to an empty string.
200
 
201
== restrict ==
202
If set to true, will not be displayed when a user is editing their own record (so, not updateable by a user). Examples would be admin and enabled, which would not be something a user should change themselves. If an admin is editing a different user, these fields are available.
203
 
204
== filter ==
11 rodolico 205
If set, this is assumed to be a regular express. The result of the input is checked against the regex. If it does not match the regex, the update is declined and an error message displayed. By default, the username can only be alpha-numeric and an underscore, so the regex '/^[a-zA-Z0-9_]+$/' is set as the filter. If a user puts a period in their password, it will be rejected. Validity of the regex is not checked.
10 rodolico 206
 
207
== size ==
208
For database creation, if set, will create a column (like varchar) with this size, ie size='128' in a varchar field will result in varchar(128)
209
 
210
== required ==
211
For database creation, sets the NOT NULL attribute to the column
212
 
213
== default ==
214
For database creation, sets the DEFAULT attribute for the column
215
 
6 rodolico 216
==== usersDataSource ====
217
 
28 rodolico 218
This is our data access class. As stated earlier, it is an abstract class, with UsersDataSourceMySQLi a class built on it.
6 rodolico 219
 
220
This code accesses the data (duh), and is consistently called $connection in the Users class. The only requirement is that it must be able to implement the following functions
221
 
222
getPassword( $username ) returns encrypted password
223
getRecord( $username ) returns array containing the values for a user
224
getAllUsers() returns an array of all user id's and names
225
getARecord( array of key value pairs to limit what is retrieved ) returns all values
226
update( array of key value pairs ) returns true/false on success/failure
227
 
228
It is also nice is it can A) handle new columns and B) create and initialize the necessary storage
229
 
230
I separated this out from the Users class because not all programs need database access. For instance, the favorites_urls app uses file based storage, so by writing a new access class for it, we will hopefully be able to get the same functionality, but with a different storage back end.
231