From b613c0f4188419d67349f3b2f3128dad274e59a1 Mon Sep 17 00:00:00 2001 From: www-data Date: Tue, 30 Jul 2024 08:50:29 +0000 Subject: [PATCH 1/5] initial implementation of Pull API for ADIF QSO data --- application/controllers/Api.php | 84 +++++++++++++++++++++++ application/models/Adif_data.php | 14 ++++ application/views/adif/data/exportapi.php | 13 ++++ 3 files changed, 111 insertions(+) create mode 100755 application/views/adif/data/exportapi.php diff --git a/application/controllers/Api.php b/application/controllers/Api.php index 8eb0e72ee..d652222b8 100644 --- a/application/controllers/Api.php +++ b/application/controllers/Api.php @@ -237,6 +237,90 @@ class API extends CI_Controller { } + function get_contacts_adif() { + + //set header + header('Content-type: application/json'); + + //load API model + $this->load->model('api_model'); + + // Decode JSON and store + $obj = json_decode(file_get_contents("php://input"), true); + if ($obj === NULL) { + http_response_code(400); + echo json_encode(['status' => 'failed', 'reason' => "wrong JSON"]); + return; + } + + //do authorization + if(!isset($obj['key']) || $this->api_model->authorize($obj['key']) == 0) { + http_response_code(401); + echo json_encode(['status' => 'failed', 'reason' => "missing api key"]); + return; + } + + //check for relevant fields in JSON input + if(!isset($obj['station_id']) or !isset($obj['goalpost'])) + { + http_response_code(400); + echo json_encode(['status' => 'failed', 'reason' => "Not all required fields were present in input JSON"]); + return; + } + + //extract relevant data to variables + $key = $obj['key']; + $station_id = $obj['station_id']; + $goalpost = $obj['goalpost']; + + //load stations API + $this->load->model('stations'); + + //get all stations of user to check if station_id should be readable + $userid = $this->api_model->key_userid($key); + $station_ids = array(); + $stations=$this->stations->all_of_user($userid); + + //extract to array + foreach ($stations->result() as $row) { + array_push($station_ids, $row->station_id); + } + + //return error if station not accessible for the API key + if(!in_array($station_id, $station_ids)) + { + http_response_code(401); + echo json_encode(['status' => 'failed', 'reason' => "Station ID not accessible for this API key"]); + return; + } + + //load adif data module + $this->load->model('adif_data'); + $data['qsos'] = $this->adif_data->export_past_goalpost($station_id, $goalpost); + $qso_count = count($data['qsos']->result()); + + //if no new QSOs are ready, return that + if($qso_count <= 0) + { + http_response_code(200); + echo json_encode(['status' => 'successfull', 'message' => 'No new QSOs available.', 'newgoalpost' => $goalpost, 'exported_qsos' => 0, 'adif' => null]); + } + + //convert data to ADIF + $adif_content = $this->load->view('adif/data/exportapi', $data, TRUE); + + //get new goalpost + $newgoalpost = 0; + foreach ($data['qsos']->result() as $row) { + $newgoalpost = max($newgoalpost, $row->COL_PRIMARY_KEY); + } + + //return API result + http_response_code(200); + echo json_encode(['status' => 'successfull', 'message' => 'Export successfull', 'newgoalpost' => $newgoalpost, 'exported_qsos' => $qso_count, 'adif' => $adif_content]); + } + + // API function to check if a callsign is in the logbook already function logbook_check_callsign() { header('Content-type: application/json'); diff --git a/application/models/Adif_data.php b/application/models/Adif_data.php index edfb96fbd..6a5691df5 100644 --- a/application/models/Adif_data.php +++ b/application/models/Adif_data.php @@ -137,6 +137,20 @@ class adif_data extends CI_Model { return $this->db->get(); } + function export_past_goalpost($station_id, $goalpost) { + //create query + $this->db->select(''.$this->config->item('table_name').'.*, station_profile.*, dxcc_entities.name as station_country'); + $this->db->from($this->config->item('table_name')); + $this->db->where($this->config->item('table_name').'.station_id', $station_id); + $this->db->where($this->config->item('table_name').".COL_PRIMARY_KEY > " . $goalpost); //only get values past the goalpost + $this->db->order_by($this->config->item('table_name').".COL_TIME_ON", "ASC"); + $this->db->join('station_profile', 'station_profile.station_id = '.$this->config->item('table_name').'.station_id'); + $this->db->join('dxcc_entities', 'station_profile.station_dxcc = dxcc_entities.adif', 'left outer'); + + //return result + return $this->db->get(); + } + function export_lotw() { $this->load->model('stations'); $active_station_id = $this->stations->find_active(); diff --git a/application/views/adif/data/exportapi.php b/application/views/adif/data/exportapi.php new file mode 100755 index 000000000..3f44b6bbc --- /dev/null +++ b/application/views/adif/data/exportapi.php @@ -0,0 +1,13 @@ +Wavelog ADIF export +3.1.4 +config->item('app_name')); ?>>config->item('app_name')."\r\n"; ?> +optionslib->get_option('version')); ?>>optionslib->get_option('version')."\r\n"; ?> + + +load->library('AdifHelper'); + +foreach ($qsos->result() as $qso) { + echo $CI->adifhelper->getAdifLine($qso); +} From c32d48f22f84eafd57ef3c6b931754401b4a4053 Mon Sep 17 00:00:00 2001 From: DB4SCW Date: Tue, 30 Jul 2024 13:27:25 +0000 Subject: [PATCH 2/5] initial implementation of Pull API for ADIF QSO data --- application/controllers/Api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/controllers/Api.php b/application/controllers/Api.php index d652222b8..def714896 100644 --- a/application/controllers/Api.php +++ b/application/controllers/Api.php @@ -297,7 +297,7 @@ class API extends CI_Controller { //load adif data module $this->load->model('adif_data'); $data['qsos'] = $this->adif_data->export_past_goalpost($station_id, $goalpost); - $qso_count = count($data['qsos']->result()); + $qso_count = count($data['qsos']->result()); //if no new QSOs are ready, return that if($qso_count <= 0) From 209424722b0df500908de775f1333f557c1cc1a5 Mon Sep 17 00:00:00 2001 From: DB4SCW Date: Tue, 30 Jul 2024 15:15:23 +0000 Subject: [PATCH 3/5] added SQL injection prevention layers --- application/controllers/Api.php | 11 +++++++++++ application/models/Adif_data.php | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/application/controllers/Api.php b/application/controllers/Api.php index def714896..063054aef 100644 --- a/application/controllers/Api.php +++ b/application/controllers/Api.php @@ -273,6 +273,17 @@ class API extends CI_Controller { $station_id = $obj['station_id']; $goalpost = $obj['goalpost']; + //check if goalpost is numeric as an additional layer of SQL injection prevention + if(!is_numeric($goalpost)) + { + http_response_code(400); + echo json_encode(['status' => 'failed', 'reason' => "Invalid goalpost."]); + return; + } + + //make sure the goalpost is an integer + $goalpost = (int)$goalpost; + //load stations API $this->load->model('stations'); diff --git a/application/models/Adif_data.php b/application/models/Adif_data.php index 6a5691df5..5e120b89a 100644 --- a/application/models/Adif_data.php +++ b/application/models/Adif_data.php @@ -142,7 +142,7 @@ class adif_data extends CI_Model { $this->db->select(''.$this->config->item('table_name').'.*, station_profile.*, dxcc_entities.name as station_country'); $this->db->from($this->config->item('table_name')); $this->db->where($this->config->item('table_name').'.station_id', $station_id); - $this->db->where($this->config->item('table_name').".COL_PRIMARY_KEY > " . $goalpost); //only get values past the goalpost + $this->db->where($this->config->item('table_name').".COL_PRIMARY_KEY > " , $goalpost); //only get values past the goalpost $this->db->order_by($this->config->item('table_name').".COL_TIME_ON", "ASC"); $this->db->join('station_profile', 'station_profile.station_id = '.$this->config->item('table_name').'.station_id'); $this->db->join('dxcc_entities', 'station_profile.station_dxcc = dxcc_entities.adif', 'left outer'); From dcabead23969350585bb7324b0181526674faf60 Mon Sep 17 00:00:00 2001 From: DB4SCW Date: Tue, 30 Jul 2024 21:00:59 +0000 Subject: [PATCH 4/5] add description comment to api endpoint and changed wording from goalpost to fetchfromid and lastfetchedid --- application/controllers/Api.php | 25 +++++++++++++++---------- application/models/Adif_data.php | 4 ++-- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/application/controllers/Api.php b/application/controllers/Api.php index 063054aef..958be0d78 100644 --- a/application/controllers/Api.php +++ b/application/controllers/Api.php @@ -237,6 +237,11 @@ class API extends CI_Controller { } + /* + * + * Function: get_contacts_adif + * Task: allows third party software to pull ADIF QSO data from wavelog after a baseline of the last fetched QSO id + */ function get_contacts_adif() { //set header @@ -261,7 +266,7 @@ class API extends CI_Controller { } //check for relevant fields in JSON input - if(!isset($obj['station_id']) or !isset($obj['goalpost'])) + if(!isset($obj['station_id']) or !isset($obj['fetchfromid'])) { http_response_code(400); echo json_encode(['status' => 'failed', 'reason' => "Not all required fields were present in input JSON"]); @@ -271,18 +276,18 @@ class API extends CI_Controller { //extract relevant data to variables $key = $obj['key']; $station_id = $obj['station_id']; - $goalpost = $obj['goalpost']; + $fetchfromid = $obj['fetchfromid']; //check if goalpost is numeric as an additional layer of SQL injection prevention - if(!is_numeric($goalpost)) + if(!is_numeric($fetchfromid)) { http_response_code(400); - echo json_encode(['status' => 'failed', 'reason' => "Invalid goalpost."]); + echo json_encode(['status' => 'failed', 'reason' => "Invalid fetchfromid."]); return; } //make sure the goalpost is an integer - $goalpost = (int)$goalpost; + $fetchfromid = (int)$fetchfromid; //load stations API $this->load->model('stations'); @@ -307,28 +312,28 @@ class API extends CI_Controller { //load adif data module $this->load->model('adif_data'); - $data['qsos'] = $this->adif_data->export_past_goalpost($station_id, $goalpost); + $data['qsos'] = $this->adif_data->export_past_id($station_id, $fetchfromid); $qso_count = count($data['qsos']->result()); //if no new QSOs are ready, return that if($qso_count <= 0) { http_response_code(200); - echo json_encode(['status' => 'successfull', 'message' => 'No new QSOs available.', 'newgoalpost' => $goalpost, 'exported_qsos' => 0, 'adif' => null]); + echo json_encode(['status' => 'successfull', 'message' => 'No new QSOs available.', 'lastfetchedid' => $fetchfromid, 'exported_qsos' => 0, 'adif' => null]); } //convert data to ADIF $adif_content = $this->load->view('adif/data/exportapi', $data, TRUE); //get new goalpost - $newgoalpost = 0; + $lastfetchedid = 0; foreach ($data['qsos']->result() as $row) { - $newgoalpost = max($newgoalpost, $row->COL_PRIMARY_KEY); + $lastfetchedid = max($lastfetchedid, $row->COL_PRIMARY_KEY); } //return API result http_response_code(200); - echo json_encode(['status' => 'successfull', 'message' => 'Export successfull', 'newgoalpost' => $newgoalpost, 'exported_qsos' => $qso_count, 'adif' => $adif_content]); + echo json_encode(['status' => 'successfull', 'message' => 'Export successfull', 'lastfetchedid' => $lastfetchedid, 'exported_qsos' => $qso_count, 'adif' => $adif_content]); } diff --git a/application/models/Adif_data.php b/application/models/Adif_data.php index 5e120b89a..f4a6694e4 100644 --- a/application/models/Adif_data.php +++ b/application/models/Adif_data.php @@ -137,12 +137,12 @@ class adif_data extends CI_Model { return $this->db->get(); } - function export_past_goalpost($station_id, $goalpost) { + function export_past_id($station_id, $fetchfromid) { //create query $this->db->select(''.$this->config->item('table_name').'.*, station_profile.*, dxcc_entities.name as station_country'); $this->db->from($this->config->item('table_name')); $this->db->where($this->config->item('table_name').'.station_id', $station_id); - $this->db->where($this->config->item('table_name').".COL_PRIMARY_KEY > " , $goalpost); //only get values past the goalpost + $this->db->where($this->config->item('table_name').".COL_PRIMARY_KEY > " , $fetchfromid); //only get values past the fetchfromid value $this->db->order_by($this->config->item('table_name').".COL_TIME_ON", "ASC"); $this->db->join('station_profile', 'station_profile.station_id = '.$this->config->item('table_name').'.station_id'); $this->db->join('dxcc_entities', 'station_profile.station_dxcc = dxcc_entities.adif', 'left outer'); From 67820c3201cebe98e4f322684f7cf764dc64dd3e Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 31 Jul 2024 04:20:13 +0000 Subject: [PATCH 5/5] po/mo updates --- assets/lang_src/messages.pot | 2 +- install/includes/gettext/lang_src/installer.pot | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/assets/lang_src/messages.pot b/assets/lang_src/messages.pot index 0c6e18756..c30bfee6d 100644 --- a/assets/lang_src/messages.pot +++ b/assets/lang_src/messages.pot @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: translations@wavelog.org\n" -"POT-Creation-Date: 2024-07-30 21:59+0000\n" +"POT-Creation-Date: 2024-07-31 04:20+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff --git a/install/includes/gettext/lang_src/installer.pot b/install/includes/gettext/lang_src/installer.pot index d64fa0e0f..632504a32 100644 --- a/install/includes/gettext/lang_src/installer.pot +++ b/install/includes/gettext/lang_src/installer.pot @@ -6,7 +6,7 @@ msgid "" msgstr "" "Report-Msgid-Bugs-To: translations@wavelog.org\n" -"POT-Creation-Date: 2024-07-30 21:59+0000\n" +"POT-Creation-Date: 2024-07-31 04:20+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n"