plugins/yubikey/usercp.php
changeset 39 6212d849ab08
parent 38 d109af008343
equal deleted inserted replaced
38:d109af008343 39:6212d849ab08
     1 <?php
       
     2 
       
     3 if ( getConfig('yubikey_enable', '1') != '1' )
       
     4 	return true;
       
     5 
       
     6 $plugins->attachHook("userprefs_jbox", "yubikey_ucp_setup();");
       
     7 $plugins->attachHook("userprefs_body", "return yubikey_user_cp(\$section);");
       
     8 $plugins->attachHook("login_form_html", "yubikey_inject_html_login();");
       
     9 $plugins->attachHook("ucp_register_form", "yubikey_inject_registration_form();");
       
    10 $plugins->attachHook("ucp_register_validate", "yubikey_register_validate(\$error);");
       
    11 $plugins->attachHook("user_registered", "yubikey_register_insert_key(\$user_id);");
       
    12 
       
    13 function yubikey_ucp_setup()
       
    14 {
       
    15 	userprefs_menu_add('usercp_sec_profile', 'yubiucp_panel_title', makeUrlNS('Special', 'Preferences/Yubikey') . '" onclick="ajaxLoginNavTo(\'Special\', \'Preferences/Yubikey\', '.USER_LEVEL_CHPREF.'); return false;');
       
    16 }
       
    17 
       
    18 function yubikey_user_cp($section)
       
    19 {
       
    20 	global $db, $session, $paths, $template, $plugins; // Common objects
       
    21 	global $lang;
       
    22 	
       
    23 	if ( $section !== 'Yubikey' )
       
    24 		return false;
       
    25 	
       
    26 	if ( $session->auth_level < USER_LEVEL_CHPREF )
       
    27 	{
       
    28 		redirect(makeUrlNS('Special', 'Login/' . $paths->fullpage, 'level=' . USER_LEVEL_CHPREF, true), 'Authentication required', 'You need to re-authenticate to access this page.', 0);
       
    29 	}
       
    30 	
       
    31 	$count_enabled = intval(getConfig('yubikey_enroll_limit', '3'));
       
    32 	
       
    33 	if ( isset($_POST['submit']) )
       
    34 	{
       
    35 		csrf_request_confirm();
       
    36 		
       
    37 		$keys = array();
       
    38 		if ( isset($_POST['yubikey_enable']) )
       
    39 		{
       
    40 			for ( $i = 0; $i < $count_enabled; $i++ )
       
    41 			{
       
    42 				if ( !empty($_POST["yubikey_otp_$i"]) )
       
    43 				{
       
    44 					$ckey =& $_POST["yubikey_otp_$i"];
       
    45 					if ( preg_match('/^[cbdefghijklnrtuv]{12,44}$/', $ckey) )
       
    46 					{
       
    47 						$ckey = substr($ckey, 0, 12);
       
    48 						$keys[] = $ckey;
       
    49 					}
       
    50 					unset($ckey);
       
    51 				}
       
    52 			}
       
    53 		}
       
    54 		// Check for double enrollment
       
    55 		$keys_check = "yubi_uid = '" . implode("' OR yubi_uid = '", $keys) . "'";
       
    56 		$q = $db->sql_query('SELECT yubi_uid FROM ' . table_prefix . "yubikey WHERE ( $keys_check ) AND user_id != {$session->user_id};");
       
    57 		if ( !$q )
       
    58 			$db->_die();
       
    59 		
       
    60 		if ( $db->numrows() > 0 )
       
    61 		{
       
    62 			echo '<div class="error-box" style="margin: 0 0 10px 0;">' . $lang->get('yubiucp_err_double_enrollment') . '</div>';
       
    63 			while ( $row = $db->fetchrow() )
       
    64 			{
       
    65 				foreach ( $keys as $i => $key )
       
    66 				{
       
    67 					if ( $key == $row['yubi_uid'] )
       
    68 					{
       
    69 						unset($keys[$i]);
       
    70 					}
       
    71 				}
       
    72 			}
       
    73 			$keys = array_values($keys);
       
    74 		}
       
    75 		$db->free_result();
       
    76 		
       
    77 		// Remove all currently registered keys
       
    78 		$q = $db->sql_query('DELETE FROM ' . table_prefix . "yubikey WHERE user_id = {$session->user_id};");
       
    79 		if ( !$q )
       
    80 			$db->_die();
       
    81 		
       
    82 		// Enroll any new keys
       
    83 		if ( !empty($keys) )
       
    84 		{
       
    85 			$query = 'INSERT INTO ' . table_prefix . "yubikey(user_id, yubi_uid) VALUES\n  " .
       
    86  								"( $session->user_id, '" . implode("' ),\n  ( $session->user_id, '", $keys) . "' );";
       
    87 			if ( !$db->sql_query($query) )
       
    88 				$db->_die();
       
    89 		}
       
    90 		
       
    91 		// Calculate flags
       
    92 		$yubi_flags = 0;
       
    93 		$yubi_flags |= intval($_POST['login_normal_flags']);
       
    94 		$yubi_flags |= intval($_POST['login_elev_flags']);
       
    95 		$yubi_flags |= ( isset($_POST['allow_no_yubikey']) ) ? YK_SEC_ALLOW_NO_OTP : 0;
       
    96 		
       
    97 		// update flags
       
    98 		$q = $db->sql_query('UPDATE ' . table_prefix . "users SET user_yubikey_flags = $yubi_flags WHERE user_id = {$session->user_id};");
       
    99 		if ( !$q )
       
   100 			$db->_die();
       
   101 		
       
   102 		// regenerate session
       
   103 		$q = $db->sql_query('SELECT password FROM ' . table_prefix . "users WHERE user_id = {$session->user_id};");
       
   104 		if ( !$q )
       
   105 			$db->_die();
       
   106 		list($password_hmac) = $db->fetchrow_num();
       
   107 		
       
   108 		@$session->register_session($session->user_id, $session->username, $password_hmac, USER_LEVEL_MEMBER, false);
       
   109 		$session->logout(USER_LEVEL_CHPREF);
       
   110 		
       
   111 		// redirect back to normal CP
       
   112 		// if OB-ing isn't enabled, require a JS redirect (hey, not many other options...)
       
   113 		if ( @ob_get_contents() )
       
   114 		{
       
   115 			@ob_end_clean();
       
   116 			redirect(makeUrlNS('Special', 'Preferences'), $lang->get('yubiucp_msg_save_title'), $lang->get('yubiucp_msg_save_body'), 3);
       
   117 		}
       
   118 		else
       
   119 		{
       
   120 			echo '<h3>' . $lang->get('yubiucp_msg_save_title') . '</h3>';
       
   121 			echo '<p>' . $lang->get('yubiucp_msg_save_body') . '</p>';
       
   122 			// not much choice here, i'm resorting to javascript because the user CP always
       
   123 			// sends headers :-/
       
   124 			echo '<script type="text/javascript">
       
   125 				addOnloadHook(function()
       
   126 				{' .
       
   127 				// note: $_COOKIE['sid'] has just been assigned by $session->register_session() - so it's safe to use here.
       
   128 				'
       
   129 					createCookie(\'sid\', \'' . $_COOKIE['sid'] . '\');
       
   130 					window.location = makeUrlNS(\'Special\', \'Preferences\');
       
   131 				});
       
   132 			</script>';
       
   133 			return true;
       
   134 		}
       
   135 	}
       
   136 	else
       
   137 	{
       
   138 		// Fetch flags
       
   139 		$q = $db->sql_query('SELECT user_yubikey_flags FROM ' . table_prefix . "users WHERE user_id = {$session->user_id};");
       
   140 		if ( !$q )
       
   141 			$db->_die();
       
   142 		
       
   143 		list($yubi_flags) = $db->fetchrow_num();
       
   144 		$yubi_flags = intval($yubi_flags);
       
   145 		// Fetch user's authorized keys from the DB
       
   146 		$q = $db->sql_query('SELECT yubi_uid FROM ' . table_prefix . "yubikey WHERE user_id = {$session->user_id};");
       
   147 		if ( !$q )
       
   148 			$db->_die();
       
   149 		
       
   150 		$keys = array();
       
   151 		while ( $row = $db->fetchrow() )
       
   152 		{
       
   153 			$keys[] = $row['yubi_uid'];
       
   154 		}
       
   155 		$db->free_result();
       
   156 	}
       
   157 	
       
   158 	while ( count($keys) < $count_enabled )
       
   159 	{
       
   160 		$keys[] = false;
       
   161 	}
       
   162 	
       
   163 	$enable_checked = ( $keys[0] === false && !isset($_POST['yubikey_enable']) ) ? '' : 'checked="checked"';
       
   164 	$displaytable = ( $keys[0] === false && !isset($_POST['yubikey_enable']) ) ? 'none' : 'block';
       
   165 	
       
   166 	$check_normal_keyonly = ( !($yubi_flags & YK_SEC_NORMAL_USERNAME) && !($yubi_flags & YK_SEC_NORMAL_PASSWORD) ) ? 'checked="checked" ' : '';
       
   167 	$check_normal_username = ( ($yubi_flags & YK_SEC_NORMAL_USERNAME) && !($yubi_flags & YK_SEC_NORMAL_PASSWORD) ) ? 'checked="checked" ' : '';
       
   168 	$check_normal_userandpw = ( ($yubi_flags & YK_SEC_NORMAL_USERNAME) && ($yubi_flags & YK_SEC_NORMAL_PASSWORD) ) ? 'checked="checked" ' : '';
       
   169 
       
   170 	$check_elev_keyonly = ( !($yubi_flags & YK_SEC_ELEV_USERNAME) && !($yubi_flags & YK_SEC_ELEV_PASSWORD) ) ? 'checked="checked" ' : '';
       
   171 	$check_elev_username = ( ($yubi_flags & YK_SEC_ELEV_USERNAME) && !($yubi_flags & YK_SEC_ELEV_PASSWORD) ) ? 'checked="checked" ' : '';
       
   172 	$check_elev_userandpw = ( ($yubi_flags & YK_SEC_ELEV_USERNAME) && ($yubi_flags & YK_SEC_ELEV_PASSWORD) ) ? 'checked="checked" ' : '';  
       
   173 	
       
   174 	?>
       
   175 	<h3 style="margin-top: 0;"><?php echo $lang->get('yubiucp_panel_title'); ?></h3>
       
   176 	
       
   177 	<form action="<?php echo makeUrlNS('Special', 'Preferences/Yubikey'); ?>" method="post">
       
   178 	
       
   179 	<div>
       
   180 		<table border="0" cellpadding="4" width="100%">
       
   181 			<tr>
       
   182 				<td style="width: 50%; text-align: right;">
       
   183 					<?php echo $lang->get('yubiucp_field_enable_title'); ?><br />
       
   184 					<small><?php echo $lang->get('yubiucp_field_enable_hint'); ?></small>
       
   185 				</td>
       
   186 				<td style="width: 50%;">
       
   187 					<label>
       
   188 						<input type="checkbox" name="yubikey_enable" onclick="if ( $(this).attr('checked') ) $('#yk_useroptions').show('blind'); else $('#yk_useroptions').hide('blind');" <?php echo $enable_checked; ?> />
       
   189 						<?php echo $lang->get('yubiucp_field_enable'); ?>
       
   190 					</label>
       
   191 				</td>
       
   192 			</tr>
       
   193 		</table>
       
   194 		<table border="0" cellpadding="4" width="100%" id="yk_useroptions" style="display: <?php echo $displaytable ?>;">
       
   195 			<tr class="yk_alt1">
       
   196 			<td style="width: 50%; text-align: right;">
       
   197 					<?php echo $lang->get('yubiucp_field_keys_title'); ?><br />
       
   198 					<small><?php
       
   199 					echo $lang->get('yubiucp_field_keys_hint');
       
   200 					if ( $count_enabled > 1 )
       
   201 					{
       
   202 						echo ' ';
       
   203 						echo $lang->get('yubiucp_field_keys_maximum', array('max' => $count_enabled));
       
   204 					}
       
   205 					?></small>
       
   206 				</td>
       
   207 				<td style="width: 50%;">
       
   208 					<?php
       
   209 					for ( $i = 0; $i < $count_enabled; $i++ )
       
   210 					{
       
   211 						echo '<p>' . generate_yubikey_field('yubikey_otp_' . $i, $keys[$i]) . '</p>';
       
   212 					}
       
   213 					?>
       
   214 				</td>
       
   215 			</tr>
       
   216 			<tr>
       
   217 				<td style="width: 50%; text-align: right;">
       
   218 					<?php echo $lang->get('yubiucp_field_normal_flags'); ?>
       
   219 				</td>
       
   220 				<td>
       
   221 					<label>
       
   222 						<input type="radio" name="login_normal_flags" value="0" <?php echo $check_normal_keyonly; ?>/>
       
   223 						<?php echo $lang->get('yubiucp_field_flags_keyonly'); ?>
       
   224 					</label>
       
   225 					
       
   226 					<br />
       
   227 					
       
   228 					<label>
       
   229 						<input type="radio" name="login_normal_flags" value="<?php echo strval(YK_SEC_NORMAL_USERNAME); ?>" <?php echo $check_normal_username; ?>/>
       
   230 						<?php echo $lang->get('yubiucp_field_flags_username'); ?>
       
   231 					</label>
       
   232 					
       
   233 					<br />
       
   234 					
       
   235 					<label>
       
   236 						<input type="radio" name="login_normal_flags" value="<?php echo strval(YK_SEC_NORMAL_USERNAME | YK_SEC_NORMAL_PASSWORD); ?>" <?php echo $check_normal_userandpw; ?>/>
       
   237 						<?php echo $lang->get('yubiucp_field_flags_userandpw'); ?>
       
   238 					</label>
       
   239 				</td>
       
   240 			</tr>
       
   241 			<tr class="yk_alt1">
       
   242 				<td style="width: 50%; text-align: right;">
       
   243 					<?php echo $lang->get('yubiucp_field_elev_flags'); ?>
       
   244 				</td>
       
   245 				<td>
       
   246 					<label>
       
   247 						<input type="radio" name="login_elev_flags" value="0" <?php echo $check_elev_keyonly; ?>/>
       
   248 						<?php echo $lang->get('yubiucp_field_flags_keyonly'); ?>
       
   249 					</label>
       
   250 					
       
   251 					<br />
       
   252 					
       
   253 					<label>
       
   254 						<input type="radio" name="login_elev_flags" value="<?php echo strval(YK_SEC_ELEV_USERNAME); ?>" <?php echo $check_elev_username; ?>/>
       
   255 						<?php echo $lang->get('yubiucp_field_flags_username'); ?>
       
   256 					</label>
       
   257 					
       
   258 					<br />
       
   259 					
       
   260 					<label>
       
   261 						<input type="radio" name="login_elev_flags" value="<?php echo strval(YK_SEC_ELEV_USERNAME | YK_SEC_ELEV_PASSWORD); ?>" <?php echo $check_elev_userandpw; ?>/>
       
   262 						<?php echo $lang->get('yubiucp_field_flags_userandpw'); ?>
       
   263 					</label>
       
   264 				</td>
       
   265 			</tr>
       
   266 			<tr>
       
   267 				<td>
       
   268 				</td>
       
   269 				<td>
       
   270 					<label>
       
   271 						<input type="checkbox" name="allow_no_yubikey" <?php if ( $yubi_flags & YK_SEC_ALLOW_NO_OTP ) echo 'checked="checked" '; ?>/>
       
   272 						<?php echo $lang->get('yubiucp_field_allow_plain_login'); ?>
       
   273 					</label>
       
   274 					<br />
       
   275 					<small>
       
   276 						<?php echo $lang->get('yubiucp_field_allow_plain_login_hint'); ?>
       
   277 					</small>
       
   278 				</td>
       
   279 			</tr>
       
   280 		</table>
       
   281 		<table border="0" cellpadding="4" width="100%">
       
   282 			<tr class="yk_alt1">
       
   283 				<td colspan="2" style="text-align: center;">
       
   284 					<input type="submit" name="submit" value="<?php echo $lang->get('etc_save_changes'); ?>" />
       
   285 				</td>
       
   286 			</tr>
       
   287 		</table>
       
   288 	</div>
       
   289 	
       
   290 	<input type="hidden" name="cstok" value="<?php echo $session->csrf_token; ?>" />
       
   291 	
       
   292 	</form>
       
   293 	<?php
       
   294 	
       
   295 	return true;
       
   296 }
       
   297 
       
   298 function yubikey_inject_html_login()
       
   299 {
       
   300 	global $lang;
       
   301 	?>
       
   302 	<tr>
       
   303 		<td class="row2">
       
   304 			<?php echo $lang->get('yubiauth_lbl_otp_field'); ?>
       
   305 		</td>
       
   306 		<td class="row1" colspan="2">
       
   307 			<input type="text" size="40" class="yubikey_noscript" name="yubikey_otp" />
       
   308 		</td>
       
   309 	</tr>
       
   310 	<?php
       
   311 }
       
   312 
       
   313 function yubikey_inject_registration_form()
       
   314 {
       
   315 	global $lang;
       
   316 	
       
   317 	$preset_otp = isset($_POST['yubikey_otp']) ? $_POST['yubikey_otp'] : false;
       
   318 	?>
       
   319 	<tr>
       
   320 		<td class="row1">
       
   321 			<?php echo $lang->get('yubiucp_reg_field_otp'); ?><br />
       
   322 			<small><?php
       
   323 				if ( getConfig('yubikey_reg_require_otp', '0') == '1' )
       
   324 					echo $lang->get('yubiucp_reg_field_otp_hint_required');
       
   325 				else
       
   326 					echo $lang->get('yubiucp_reg_field_otp_hint_optional');
       
   327 			?></small>
       
   328 		</td>
       
   329 		<td class="row1">
       
   330 			<?php
       
   331 			echo generate_yubikey_field('yubikey_otp', $preset_otp);
       
   332 			?>
       
   333 		</td>
       
   334 		<td class="row1">
       
   335 		</td>
       
   336 	</tr>
       
   337 	<?php
       
   338 }
       
   339 
       
   340 function yubikey_register_validate(&$error)
       
   341 {
       
   342 	global $db, $session, $paths, $template, $plugins; // Common objects
       
   343 	global $lang;
       
   344 	
       
   345 	$otp_required = getConfig('yubikey_reg_require_otp', '0') == '1';
       
   346 	$have_otp = !empty($_POST['yubikey_otp']);
       
   347 	if ( $otp_required && !$have_otp )
       
   348 	{
       
   349 		$error = $lang->get('yubiucp_reg_err_otp_required');
       
   350 		return false;
       
   351 	}
       
   352 	if ( $have_otp )
       
   353 	{
       
   354 		$result = yubikey_validate_otp($_POST['yubikey_otp']);
       
   355 		if ( !$result['success'] )
       
   356 		{
       
   357 			$error = '<b>' . $lang->get('yubiucp_reg_err_otp_invalid') . '</b><br />' . $lang->get("yubiauth_err_{$result['error']}");
       
   358 			return false;
       
   359 		}
       
   360 		// check for double enrollment
       
   361 		$yubi_uid = substr($_POST['yubikey_otp'], 0, 12);
       
   362 		// Note on SQL injection: yubikey_validate_otp() has already ensured that this is safe
       
   363 		$q = $db->sql_query('SELECT 1 FROM ' . table_prefix . "yubikey WHERE yubi_uid = '$yubi_uid';");
       
   364 		if ( !$q )
       
   365 			$db->_die();
       
   366 		if ( $db->numrows() > 0 )
       
   367 		{
       
   368 			$error = '<b>' . $lang->get('yubiucp_reg_err_otp_invalid') . '</b><br />' . $lang->get('yubiucp_err_double_enrollment_single');
       
   369 			return false;
       
   370 		}
       
   371 		$db->free_result();
       
   372 	}
       
   373 }
       
   374 
       
   375 function yubikey_register_insert_key($user_id)
       
   376 {
       
   377 	global $db, $session, $paths, $template, $plugins; // Common objects
       
   378 	if ( !empty($_POST['yubikey_otp']) )
       
   379 	{
       
   380 		$yubi_uid = $db->escape(substr($_POST['yubikey_otp'], 0, 12));
       
   381 		$q = $db->sql_query('INSERT INTO ' . table_prefix . "yubikey ( user_id, yubi_uid ) VALUES ( $user_id, '$yubi_uid' );");
       
   382 		if ( !$q )
       
   383 			$db->_die();
       
   384 	}
       
   385 }