author | Dan |
Sun, 01 Mar 2009 20:41:17 -0500 | |
changeset 5 | 2114640729a5 |
parent 3 | d0fe7acaf0e8 |
child 17 | e04c0f64e972 |
permissions | -rw-r--r-- |
0 | 1 |
<?php |
2 |
||
3
d0fe7acaf0e8
Maybe we could actually make yubikey_enable in config not ignored!
Dan
parents:
2
diff
changeset
|
3 |
if ( getConfig('yubikey_enable', '1') != '1' ) |
d0fe7acaf0e8
Maybe we could actually make yubikey_enable in config not ignored!
Dan
parents:
2
diff
changeset
|
4 |
return true; |
d0fe7acaf0e8
Maybe we could actually make yubikey_enable in config not ignored!
Dan
parents:
2
diff
changeset
|
5 |
|
0 | 6 |
// hook into auth |
7 |
$plugins->attachHook('login_process_userdata_json', 'return yubikey_auth_hook_json($userinfo, $req["level"], @$req["remember"]);'); |
|
8 |
// hook into special page init |
|
9 |
$plugins->attachHook('session_started', 'yubikey_add_special_pages();'); |
|
10 |
||
11 |
function yubikey_auth_hook_json(&$userdata, $level, $remember) |
|
12 |
{ |
|
13 |
global $db, $session, $paths, $template, $plugins; // Common objects |
|
14 |
global $lang; |
|
15 |
||
16 |
$do_validate_otp = false; |
|
17 |
$do_validate_user = false; |
|
18 |
$do_validate_pass = false; |
|
19 |
||
20 |
$user_flag = ( $level >= USER_LEVEL_CHPREF ) ? YK_SEC_ELEV_USERNAME : YK_SEC_NORMAL_USERNAME; |
|
21 |
$pass_flag = ( $level >= USER_LEVEL_CHPREF ) ? YK_SEC_ELEV_PASSWORD : YK_SEC_NORMAL_PASSWORD; |
|
22 |
||
23 |
$auth_log_prefix = ( $level >= USER_LEVEL_CHPREF ) ? 'admin_' : ''; |
|
24 |
||
2
6edc6ebb3b39
Minor: if input OTP is empty and password looks like OTP, now copies password to OTP in memory and treats password field as OTP. Will require patch in Enano trunk to work for html login.
Dan
parents:
0
diff
changeset
|
25 |
// Sort of a hack: if the password looks like an OTP and the OTP field is empty, use the password as the OTP |
6edc6ebb3b39
Minor: if input OTP is empty and password looks like OTP, now copies password to OTP in memory and treats password field as OTP. Will require patch in Enano trunk to work for html login.
Dan
parents:
0
diff
changeset
|
26 |
if ( empty($userdata['yubikey_otp']) && preg_match('/^[cbdefghijklnrtuv]{44}$/', $userdata['password'] ) ) |
6edc6ebb3b39
Minor: if input OTP is empty and password looks like OTP, now copies password to OTP in memory and treats password field as OTP. Will require patch in Enano trunk to work for html login.
Dan
parents:
0
diff
changeset
|
27 |
{ |
6edc6ebb3b39
Minor: if input OTP is empty and password looks like OTP, now copies password to OTP in memory and treats password field as OTP. Will require patch in Enano trunk to work for html login.
Dan
parents:
0
diff
changeset
|
28 |
$userdata['yubikey_otp'] = $userdata['password']; |
6edc6ebb3b39
Minor: if input OTP is empty and password looks like OTP, now copies password to OTP in memory and treats password field as OTP. Will require patch in Enano trunk to work for html login.
Dan
parents:
0
diff
changeset
|
29 |
} |
6edc6ebb3b39
Minor: if input OTP is empty and password looks like OTP, now copies password to OTP in memory and treats password field as OTP. Will require patch in Enano trunk to work for html login.
Dan
parents:
0
diff
changeset
|
30 |
|
0 | 31 |
if ( !empty($userdata['username']) ) |
32 |
{ |
|
33 |
// get flags |
|
34 |
$q = $db->sql_query('SELECT user_id, user_yubikey_flags FROM ' . table_prefix . "users WHERE " . ENANO_SQLFUNC_LOWERCASE . "(username) = '" . $db->escape(strtolower($userdata['username'])) . "';"); |
|
35 |
if ( !$q ) |
|
36 |
$db->die_json(); |
|
37 |
||
38 |
if ( $db->numrows() < 1 ) |
|
39 |
{ |
|
40 |
// Username not found - let the main login function handle it |
|
41 |
$db->free_result(); |
|
42 |
return null; |
|
43 |
} |
|
44 |
list($user_id, $flags) = $db->fetchrow_num(); |
|
45 |
$flags = intval($flags); |
|
46 |
// At the point the username is validated. |
|
47 |
$do_validate_user = false; |
|
48 |
$do_validate_pass = $flags & $pass_flag; |
|
49 |
if ( empty($userdata['yubikey_otp']) ) |
|
50 |
{ |
|
51 |
// no OTP was provided |
|
52 |
// make sure the user has allowed logging in with no OTP |
|
53 |
if ( !($flags & YK_SEC_ALLOW_NO_OTP) ) |
|
54 |
{ |
|
55 |
// We also might have no Yubikeys enrolled. |
|
56 |
$q = $db->sql_query('SELECT 1 FROM ' . table_prefix . "yubikey WHERE user_id = $user_id;"); |
|
57 |
if ( !$q ) |
|
58 |
$db->die_json(); |
|
59 |
||
60 |
if ( $db->numrows() > 0 ) |
|
61 |
{ |
|
62 |
// Yep at least one key is enrolled. |
|
63 |
// I don't think these should be logged because they'll usually just be innocent mistakes. |
|
64 |
$db->free_result(); |
|
65 |
return array( |
|
66 |
'mode' => 'error', |
|
67 |
'error' => 'yubiauth_err_must_have_otp' |
|
68 |
); |
|
69 |
} |
|
70 |
// Nope, no keys enrolled, user hasn't enabled Yubikey support |
|
71 |
$db->free_result(); |
|
72 |
} |
|
73 |
// we're ok, use normal password auth |
|
74 |
return null; |
|
75 |
} |
|
76 |
else |
|
77 |
{ |
|
78 |
// user did enter an OTP |
|
79 |
$do_validate_otp = true; |
|
80 |
} |
|
81 |
} |
|
82 |
else if ( !empty($userdata['yubikey_otp']) ) |
|
83 |
{ |
|
84 |
// we have an OTP, but no username to work with |
|
85 |
$yubi_uid = substr($userdata['yubikey_otp'], 0, 12); |
|
86 |
if ( !preg_match('/^[cbdefghijklnrtuv]{12}$/', $yubi_uid ) ) |
|
87 |
{ |
|
88 |
return array( |
|
89 |
'mode' => 'error', |
|
90 |
'error' => 'yubiauth_err_invalid_otp' |
|
91 |
); |
|
92 |
} |
|
93 |
$q = $db->sql_query('SELECT u.user_id, u.username, u.user_yubikey_flags FROM ' . table_prefix . "users AS u\n" |
|
94 |
. " LEFT JOIN " . table_prefix . "yubikey AS y\n" |
|
95 |
. " ON ( y.user_id = u.user_id )\n" |
|
96 |
. " WHERE y.yubi_uid = '$yubi_uid'\n" |
|
97 |
. " GROUP BY u.user_yubikey_flags;"); |
|
98 |
if ( !$q ) |
|
99 |
$db->_die(); |
|
100 |
||
101 |
if ( $db->numrows() < 1 ) |
|
102 |
{ |
|
103 |
if ( !$do_validate_pass ) |
|
104 |
$session->sql('INSERT INTO ' . table_prefix . "logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES\n" |
|
105 |
. ' (\'security\', \'' . $auth_log_prefix . 'auth_bad\', '.time().', \''.enano_date('d M Y h:i a').'\', \'(Yubikey)\', ' |
|
106 |
. '\''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')'); |
|
107 |
||
108 |
return array( |
|
109 |
'mode' => 'error', |
|
110 |
'error' => 'yubiauth_err_key_not_authorized' |
|
111 |
); |
|
112 |
} |
|
113 |
||
114 |
list($user_id, $username, $flags) = $db->fetchrow_num(); |
|
115 |
$do_validate_otp = true; |
|
116 |
$do_validate_user = $flags & $user_flag; |
|
117 |
$do_validate_pass = $flags & $pass_flag; |
|
118 |
} |
|
119 |
else |
|
120 |
{ |
|
121 |
// Nothing - no username or OTP. This request can't be used; throw it out. |
|
122 |
return array( |
|
123 |
'mode' => 'error', |
|
124 |
'error' => 'yubiauth_err_nothing_provided' |
|
125 |
); |
|
126 |
} |
|
127 |
if ( $do_validate_otp ) |
|
128 |
{ |
|
129 |
// We need to validate the OTP. |
|
130 |
$otp_check = yubikey_validate_otp($userdata['yubikey_otp']); |
|
131 |
if ( !$otp_check['success'] ) |
|
132 |
{ |
|
133 |
if ( !$do_validate_pass ) |
|
134 |
$session->sql('INSERT INTO ' . table_prefix . "logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES\n" |
|
135 |
. ' (\'security\', \'' . $auth_log_prefix . 'auth_bad\', '.time().', \''.enano_date('d M Y h:i a').'\', \'(Yubikey)\', ' |
|
136 |
. '\''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')'); |
|
137 |
return array( |
|
138 |
'mode' => 'error', |
|
139 |
'error' => 'yubiauth_err_' . $otp_check['error'] |
|
140 |
); |
|
141 |
} |
|
142 |
} |
|
143 |
if ( $do_validate_user ) |
|
144 |
{ |
|
145 |
if ( empty($username) ) |
|
146 |
{ |
|
147 |
return array( |
|
148 |
'mode' => 'error', |
|
149 |
'error' => 'yubiauth_err_must_have_username' |
|
150 |
); |
|
151 |
} |
|
152 |
if ( strtolower($username) !== strtolower($userdata['username']) ) |
|
153 |
{ |
|
154 |
// Username incorrect |
|
155 |
if ( !$do_validate_pass ) |
|
156 |
$session->sql('INSERT INTO ' . table_prefix . "logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES\n" |
|
157 |
. ' (\'security\', \'' . $auth_log_prefix . 'auth_bad\', '.time().', \''.enano_date('d M Y h:i a').'\', \'(Yubikey)\', ' |
|
158 |
. '\''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')'); |
|
159 |
return array( |
|
160 |
'mode' => 'error', |
|
161 |
'error' => 'invalid_credentials' |
|
162 |
); |
|
163 |
} |
|
164 |
} |
|
165 |
// Do we need to have the password validated? |
|
166 |
if ( $do_validate_pass ) |
|
167 |
{ |
|
5 | 168 |
if ( empty($userdata['password']) ) |
169 |
{ |
|
170 |
return array( |
|
171 |
'mode' => 'error', |
|
172 |
'error' => 'yubiauth_err_must_have_password' |
|
173 |
); |
|
174 |
} |
|
0 | 175 |
// Yes; return and let the login API continue |
176 |
return null; |
|
177 |
} |
|
178 |
else |
|
179 |
{ |
|
180 |
// No password required; validated, issue session key |
|
181 |
$session->sql('INSERT INTO ' . table_prefix . "logs(log_type,action,time_id,date_string,author,edit_summary,page_text) VALUES\n" |
|
182 |
. ' (\'security\', \'' . $auth_log_prefix . 'auth_good\', '.time().', \''.enano_date('d M Y h:i a').'\', \'' . $db->escape($userdata['username']) . '\', ' |
|
183 |
. '\''.$db->escape($_SERVER['REMOTE_ADDR']).'\', ' . intval($level) . ')'); |
|
184 |
||
185 |
$q = $db->sql_query('SELECT password FROM ' . table_prefix . "users WHERE user_id = $user_id;"); |
|
186 |
if ( !$q ) |
|
187 |
$db->_die(); |
|
188 |
||
189 |
list($password) = $db->fetchrow_num(); |
|
190 |
$db->free_result(); |
|
191 |
||
192 |
$session->register_session($user_id, $userdata['username'], $password, $level, $remember); |
|
193 |
return true; |
|
194 |
} |
|
195 |
} |
|
196 |
||
197 |
function yubikey_add_special_pages() |
|
198 |
{ |
|
199 |
global $db, $session, $paths, $template, $plugins; // Common objects |
|
200 |
global $lang; |
|
201 |
||
3
d0fe7acaf0e8
Maybe we could actually make yubikey_enable in config not ignored!
Dan
parents:
2
diff
changeset
|
202 |
if ( getConfig('yubikey_enable', '1') != '1' ) |
d0fe7acaf0e8
Maybe we could actually make yubikey_enable in config not ignored!
Dan
parents:
2
diff
changeset
|
203 |
return true; |
d0fe7acaf0e8
Maybe we could actually make yubikey_enable in config not ignored!
Dan
parents:
2
diff
changeset
|
204 |
|
0 | 205 |
$paths->add_page(array( |
206 |
'name' => $lang->get('yubiauth_specialpage_yubikey'), |
|
207 |
'urlname' => 'Yubikey', |
|
208 |
'namespace' => 'Special', |
|
209 |
'visible' => 0, 'protected' => 0, 'comments_on' => 0, 'special' => 0 |
|
210 |
)); |
|
211 |
} |
|
212 |
||
213 |
function page_Special_Yubikey() |
|
214 |
{ |
|
215 |
global $db, $session, $paths, $template, $plugins; // Common objects |
|
216 |
||
217 |
header('Content-type: text/javascript'); |
|
218 |
/* |
|
219 |
if ( isset($_GET['validate_otp']) ) |
|
220 |
{ |
|
221 |
echo enano_json_encode(yubikey_validate_otp($_GET['validate_otp'])); |
|
222 |
return true; |
|
223 |
} |
|
224 |
*/ |
|
225 |
if ( isset($_GET['get_flags']) || isset($_POST['get_flags']) ) |
|
226 |
{ |
|
227 |
$yubi_uid = substr($_REQUEST['get_flags'], 0, 12); |
|
228 |
if ( !preg_match('/^[cbdefghijklnrtuv]{12}$/', $yubi_uid) ) |
|
229 |
{ |
|
230 |
return print enano_json_encode(array( |
|
231 |
'mode' => 'error', |
|
232 |
'error' => 'invalid_otp' |
|
233 |
)); |
|
234 |
} |
|
235 |
$q = $db->sql_query('SELECT u.user_yubikey_flags FROM ' . table_prefix . "users AS u\n" |
|
236 |
. " LEFT JOIN " . table_prefix . "yubikey AS y\n" |
|
237 |
. " ON ( y.user_id = u.user_id )\n" |
|
238 |
. " WHERE y.yubi_uid = '$yubi_uid'\n" |
|
239 |
. " GROUP BY u.user_yubikey_flags;"); |
|
240 |
if ( !$q ) |
|
241 |
$db->_die(); |
|
242 |
||
243 |
if ( $db->numrows() < 1 ) |
|
244 |
{ |
|
245 |
return print enano_json_encode(array( |
|
246 |
'mode' => 'error', |
|
247 |
'error' => 'key_not_authorized' |
|
248 |
)); |
|
249 |
} |
|
250 |
||
251 |
list($flags) = $db->fetchrow_num(); |
|
252 |
||
253 |
echo enano_json_encode(array( |
|
254 |
// We strip YK_SEC_ALLOW_NO_OTP here for security reasons. |
|
255 |
'flags' => intval($flags & ~YK_SEC_ALLOW_NO_OTP) |
|
256 |
)); |
|
257 |
||
258 |
return true; |
|
259 |
} |
|
260 |
} |
|
261 |