diff -r 351d40b21cbc -r b9eb748ac1e4 yms/yms.php --- a/yms/yms.php Fri Apr 08 17:23:16 2016 -0400 +++ b/yms/yms.php Mon Apr 11 11:23:30 2016 -0400 @@ -104,6 +104,201 @@ $result = yms_add_yubikey($_POST['add_aes'], $_POST['add_otp'], $client_id, $enabled, $any_client, $notes); yms_send_response('yms_msg_addkey_success', $result); } + else if ( isset($_POST['csv']) ) + { + $csv = explode("\n", trim($_POST['csv'])); + + $errors = array(); + + // first line: header + $head = str_getcsv($csv[0]); + + // column check: aes_secret + if ( !in_array('aes_secret', $head) ) + { + $errors[] = $lang->get('yms_err_add_batch_missing_aes_key'); + } + + // column check: otp, public_id and private_id + if ( !in_array('otp', $head) ) + { + if ( !in_array('public_id', $head) || !in_array('private_id', $head) ) + { + $errors[] = $lang->get('yms_err_add_batch_missing_id'); + } + } + + if ( !empty($errors) ) + { + yms_send_response(false, ''); + } + + // we are good to start processing + $db->transaction_begin(); + for ( $i = 1; $i < count($csv); $i++ ) + { + $line = str_getcsv($csv[$i]); + + // ensure column count == row count + if ( count($line) !== count($head) ) + { + $errors[] = $lang->get('yms_err_add_batch_bad_row_count', array('line' => $i)); + continue; + } + + // remap line + foreach ( $head as $j => $col ) + { + $line[$col] =& $line[$j]; + } + + // initialize row + $row = array( + 'client_id' => $yms_client_id, + 'aes_secret' => yms_hex_encode(yms_tobinary($line['aes_secret'])), + 'session_count' => 0, + 'token_count' => 0, + 'create_time' => time(), + 'token_time' => 0, + 'flags' => 0, + 'notes' => '' + ); + + if ( !preg_match('/^[0-9a-f]{32}$/', $row['aes_secret']) ) + { + $errors[] = $lang->get('yms_err_add_batch_aes_secret', array('line' => $i)); + continue; + } + + // do we have an OTP? + if ( isset($line['otp']) ) + { + // yes, decode it + $otp = yms_decode_otp($line['otp'], $line['aes_secret']); + if ( $otp === false ) + { + $errors[] = $lang->get('yms_err_add_batch_bad_otp', array('line' => $i)); + continue; + } + + if ( !$otp['crc_good'] ) + { + $errors[] = $lang->get('yms_err_add_batch_bad_otp', array('line' => $i)); + continue; + } + + $row['public_id'] = $otp['publicid']; + $row['private_id'] = $otp['privateid']; + $row['session_count'] = $otp['session']; + $row['token_count'] = $otp['count']; + $row['token_time'] = $otp['timestamp']; + } + + // public and private ID + foreach ( array('public_id', 'private_id') as $col ) + { + if ( !empty($line[$col]) ) + { + $row[$col] = yms_hex_encode(yms_tobinary($line[$col])); + } + if ( !isset($row[$col]) || !preg_match('/^[0-9a-f]{12}$/', $row[$col]) ) + { + $errors[] = $lang->get("yms_err_add_batch_bad_$col", array('line' => $i)); + continue 2; + } + } + + // session count, token count and timestamp + foreach ( array('session_count', 'token_count', 'token_time') as $col ) + { + if ( !empty($line[$col]) ) + { + $row[$col] = intval($line[$col]); + } + } + + // notes + if ( isset($line['notes']) ) + { + $row['notes'] = trim($line['notes']); + } + + // lifecycle state + if ( isset($line['lifecycle_state']) ) + { + if ( !in_array($line['lifecycle_state'], array('active', 'inactive')) ) + { + $errors[] = $lang->get('yms_err_add_batch_bad_lifecycle_state', array('line' => $i)); + continue; + } + + if ( $line['lifecycle_state'] === 'active' ) + { + $row['flags'] |= YMS_ENABLED; + } + } + else + { + // default to active + $row['flags'] |= YMS_ENABLED; + } + + // global access + if ( isset($line['access']) ) + { + if ( !in_array($line['access'], array('global', 'restricted')) ) + { + $errors[] = $lang->get('yms_err_add_batch_bad_access', array('line' => $i)); + continue; + } + + if ( $line['access'] === 'global' ) + { + $row['flags'] |= YMS_ANY_CLIENT; + } + } + + // duplicate key check + $q = $db->sql_query('SELECT 1 FROM ' . table_prefix . "yms_yubikeys WHERE public_id = '{$row['public_id']}';"); + if ( $db->numrows() > 0 ) + { + $errors[] = $lang->get('yms_err_add_batch_duplicate', array('line' => $i, 'public_id' => $row['public_id'])); + continue; + } + + // build query + $cols = implode(', ', array_keys($row)); + + foreach ( $row as &$cell ) + { + if ( is_string($cell) ) + { + $cell = "'" . $db->escape($cell) . "'"; + } + } + unset($cell); + + $query = sprintf("INSERT INTO %syms_yubikeys ( %s ) VALUES ( %s );", + table_prefix, + $cols, + implode(', ', $row) + ); + + // insert it! + $q = $db->sql_query($query); + if ( $q ) + { + $errors[] = $lang->get('yms_err_add_batch_success', array('line' => $i, 'public_id' => $row['public_id'])); + } + else + { + $errors[] = $lang->get('yms_err_add_batch_query', array('line' => $i, 'public_id' => $row['public_id'], 'error' => $db->sql_error())); + } + } + $db->transaction_commit(); + + yms_send_response('

' . $lang->get('yms_lbl_add_batch_success_head') . '

', true); + } else if ( isset($_POST['claim_otp']) && getConfig('yms_claim_enable', 0) == 1 ) { // do we need to validate a custom field? @@ -177,6 +372,10 @@ href="" onclick="yms_showpage('AddKey'); return false;"> get('yms_btn_add_key'); ?> + + get('yms_btn_add_batch'); ?> + 0 ): ?> @@ -388,6 +587,53 @@ $output->footer(); } +// Add multiple Yubikeys by uploading a CSV +function page_Special_YMS_AddKeyBatch() +{ + global $db, $session, $paths, $template, $plugins; // Common objects + global $lang, $output; + + $output->add_after_header(''); + + $output->header(); + ?> + +

get('yms_lbl_add_batch_heading'); ?>

+ get('yms_lbl_add_batch_desc'); ?> + +
+ +
+ + + + + + + + + + + +
+ get('yms_lbl_add_batch_field_csv'); ?>
+ get('yms_lbl_add_batch_field_csv_hint'); ?> +
+ +
+ +
+
+ +
+ + footer(); +} + // Add key, using just an OTP // Requires the key to be in the database as client ID 0 function page_Special_YMS_AddPreregisteredKey() @@ -855,9 +1101,6 @@ if ( !$q ) $db->die_json(); - if ( $db->sql_affectedrows() < 1 ) - echo 'no affected rows; not '; - echo 'ok'; }