aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeffrey Armstrong <jeff@approximatrix.com>2022-04-11 16:28:43 -0400
committerJeffrey Armstrong <jeff@approximatrix.com>2022-04-11 16:28:43 -0400
commit4392961dd95582b91e173f9ae40ac510b9afe7d4 (patch)
tree253e6d2b7ea70b21074575af94d194ed4ec48571
parent26a936137f67843cb773bc9b9e8c360d5abff65f (diff)
downloadlevitating-4392961dd95582b91e173f9ae40ac510b9afe7d4.tar.gz
levitating-4392961dd95582b91e173f9ae40ac510b9afe7d4.zip
Added token validation to all api calls, esp. checkins. Changed status reports to use better query structure. Added query derived types to the request derived types directly. Requires testing of actual builds.
-rw-r--r--captain/api.f90127
-rw-r--r--captain/queryutils.f9072
-rw-r--r--captain/response.f9037
-rw-r--r--captain/security.f9039
-rw-r--r--player/endpoints.f9030
5 files changed, 212 insertions, 93 deletions
diff --git a/captain/api.f90 b/captain/api.f90
index 665ff8d..c05b0ba 100644
--- a/captain/api.f90
+++ b/captain/api.f90
@@ -59,20 +59,24 @@ contains
class(request)::req
integer::job_i, task_i
- if(associated(req%query_string)) then
+ character(len=:), pointer::status
+
+ status => req%q%get_value("status")
+
+ if(associated(status)) then
job_i = req%path_component_int(5)
task_i = req%path_component_int(7)
- call write_log("Task Update Is "//trim(req%query_string), LOG_DEBUG)
- if(req%query_string == "starting") then
+ call write_log("Task Update Is "//trim(status), LOG_DEBUG)
+ if(status == "starting") then
call write_log("Inserting task", LOG_DEBUG)
call insert_task(job_i, task_i)
call update_job_status(job_i, JOB_STATUS_WORKING)
- else if(req%query_string == "inprogress") then
+ else if(status == "inprogress") then
call update_task_status(job_i, task_i, JOB_STATUS_WORKING)
call update_job_status(job_i, JOB_STATUS_WORKING)
- else if(req%query_string == "complete") then
+ else if(status == "complete") then
call update_task_status(job_i, task_i, JOB_STATUS_SUCCESS)
- else if(req%query_string == "failed") then
+ else if(status == "failed") then
call update_task_status(job_i, task_i, JOB_STATUS_FAILURE)
end if
end if
@@ -84,7 +88,7 @@ contains
use captain_db
use special_filenames
use logging
- use query_utilities
+ use security, only: validate_query_token
implicit none
type(request), intent(in)::req
@@ -94,73 +98,88 @@ contains
integer::job_i, player_i, qs_platform_index
character(len=:), pointer::checkin_work_json
- type(query)::q
! Complete - "/api/player/{name}/job/{jobid}/complete"
! Failed - "/api/player/{name}/job/{jobid}/failed"
! Task - "/api/player/{name}/job/{jobid}/task/{task num}"
if(trim(req%component(2)) == "player" .and. trim(req%component(4)) == "job") then
- job_i = req%path_component_int(5)
- call write_log("Job "//trim(req%component(5))//" update arrived", LOG_INFO)
- write(player, *) job_i
-
- if(.not. is_final_job_status(job_i)) then
- if(trim(req%component(6)) == "complete") then
- call update_job_status(job_i, JOB_STATUS_SUCCESS)
- else if(trim(req%component(6)) == "failure") then
- call update_job_status(job_i, JOB_STATUS_FAILURE)
+ if(validate_query_token(req%q%get_value("token"), req%component(3))) then
+
+ job_i = req%path_component_int(5)
+ call write_log("Job "//trim(req%component(5))//" update arrived", LOG_INFO)
+
+ write(player, *) job_i
+
+ if(.not. is_final_job_status(job_i)) then
+ if(trim(req%component(6)) == "complete") then
+ call update_job_status(job_i, JOB_STATUS_SUCCESS)
+ else if(trim(req%component(6)) == "failure") then
+ call update_job_status(job_i, JOB_STATUS_FAILURE)
+ end if
end if
- end if
+
+ if(trim(req%component(6)) == "task") then
+ call write_log("Task update encountered", LOG_INFO)
+ call handle_task_request(req)
+ end if
+
+ resp%code = GEMINI_CODE_SUCCESS
+ call resp%set_body_contents(RESPONSE_JSON_OKAY)
+ resp%body_mimetype = "text/plain"
+
+ else
- if(trim(req%component(6)) == "task") then
- call write_log("Task update encountered", LOG_INFO)
- call handle_task_request(req)
+ resp%code = GEMINI_CODE_BAD_REQUEST
+
end if
-
- resp%code = GEMINI_CODE_SUCCESS
- call resp%set_body_contents(RESPONSE_JSON_OKAY)
- resp%body_mimetype = "text/plain"
! Checkin - /api/player/{name}/checkin.json
else if(trim(req%component(2)) == "player" .and. trim(req%component(4)) == "checkin.json") then
- ! Check for pending jobs
- call req%path_component(3, player)
- player_i = get_player_id(player)
-
- ! If we have a checkin, but the worker should have a job in progress, mark
- ! the jobs as failed.
- call mark_working_jobs_as_failed(player_i)
+
+ if(validate_query_token(req%q%get_value("token"), req%component(3))) then
- ! Acknowledge the checkin in the database
- if(associated(req%query_string)) then
- call q%init(req%query_string)
- if(associated(q%get_value("platform"))) then
- call acknowledge_checkin(player_i, q%get_value("platform"))
+ ! Check for pending jobs
+ call req%path_component(3, player)
+ player_i = get_player_id(player)
+
+ ! If we have a checkin, but the worker should have a job in progress, mark
+ ! the jobs as failed.
+ call mark_working_jobs_as_failed(player_i)
+
+ ! Acknowledge the checkin in the database
+ if(associated(req%query_string)) then
+ if(associated(req%q%get_value("platform"))) then
+ call acknowledge_checkin(player_i, req%q%get_value("platform"))
+ else
+ call acknowledge_checkin(player_i)
+ end if
else
call acknowledge_checkin(player_i)
end if
- call q%destroy()
- else
- call acknowledge_checkin(player_i)
- end if
-
- job_i = get_pending_job_for_player(player_i)
- if(job_i < 0) then
- resp%code = GEMINI_CODE_SUCCESS
- call resp%set_body_contents(RESPONSE_JSON_IDLE)
- else
- checkin_work_json => build_job_available_json(job_i)
- if(associated(checkin_work_json)) then
+
+ job_i = get_pending_job_for_player(player_i)
+ if(job_i < 0) then
resp%code = GEMINI_CODE_SUCCESS
- call write_log("Sending: "//trim(checkin_work_json), LOG_DEBUG)
- call resp%set_body_contents(trim(checkin_work_json), "text/gemini")
- deallocate(checkin_work_json)
+ call resp%set_body_contents(RESPONSE_JSON_IDLE)
else
- resp%code = GEMINI_CODE_PERMFAIL
+ checkin_work_json => build_job_available_json(job_i)
+ if(associated(checkin_work_json)) then
+ resp%code = GEMINI_CODE_SUCCESS
+ call write_log("Sending: "//trim(checkin_work_json), LOG_DEBUG)
+ call resp%set_body_contents(trim(checkin_work_json), "text/gemini")
+ deallocate(checkin_work_json)
+ else
+ resp%code = GEMINI_CODE_PERMFAIL
+ end if
end if
- end if
+ else
+
+ resp%code = GEMINI_CODE_BAD_REQUEST
+
+ end if
+
! Instruction - /api/instructions/{name}
else if(trim(req%component(2)) == "instruction") then
diff --git a/captain/queryutils.f90 b/captain/queryutils.f90
index 00a914b..c6b612b 100644
--- a/captain/queryutils.f90
+++ b/captain/queryutils.f90
@@ -36,7 +36,7 @@ implicit none
end type query_component
type :: query
-
+
character(len=:), pointer::full
type(query_component), dimension(:), pointer::components
@@ -133,42 +133,54 @@ contains
implicit none
class(query), intent(out)::self
- character(len=*), intent(in)::str
+ character(len=*), intent(in), optional::str
character(64)::msg
integer::ampersands, i, i_end, i_comp, n
- n = len_trim(str)
- allocate(character(len=n) :: self%full)
- self%full = str
-
- ampersands = 0
- do i = 1, len(self%full)
- if(self%full(i:i) == '&') then
- ampersands = ampersands + 1
- end if
- end do
-
- allocate(self%components(ampersands + 1))
+ self%components => null()
+ self%full => null()
- ! Split and parse each component
- if(ampersands == 0) then
- call self%components(1)%parse(self%full)
+ if(present(str)) then
+ n = len_trim(str)
else
- i_comp = 1
- i = 1
- i_end = index(self%full, '&')
- do while(i_comp < ampersands + 1)
- call self%components(i_comp)%parse(self%full(i:i_end-1))
- i = i_end + 1
- do i_end = i, len_trim(self%full)
- if(self%full(i_end:i_end) == '&') then
- exit
- end if
- end do
- i_comp = i_comp + 1
+ n = 0
+ end if
+
+ if(n > 0) then
+
+ allocate(character(len=n) :: self%full)
+ self%full = str
+
+ ampersands = 0
+ do i = 1, len(self%full)
+ if(self%full(i:i) == '&') then
+ ampersands = ampersands + 1
+ end if
end do
- call self%components(i_comp)%parse(self%full(i:i_end-1))
+
+ allocate(self%components(ampersands + 1))
+
+ ! Split and parse each component
+ if(ampersands == 0) then
+ call self%components(1)%parse(self%full)
+ else
+ i_comp = 1
+ i = 1
+ i_end = index(self%full, '&')
+ do while(i_comp < ampersands + 1)
+ call self%components(i_comp)%parse(self%full(i:i_end-1))
+ i = i_end + 1
+ do i_end = i, len_trim(self%full)
+ if(self%full(i_end:i_end) == '&') then
+ exit
+ end if
+ end do
+ i_comp = i_comp + 1
+ end do
+ call self%components(i_comp)%parse(self%full(i:i_end-1))
+ end if
+
end if
end subroutine query_init
diff --git a/captain/response.f90 b/captain/response.f90
index 2659c31..3933eb8 100644
--- a/captain/response.f90
+++ b/captain/response.f90
@@ -22,13 +22,15 @@
module server_response
use iso_c_binding
+use query_utilities
implicit none
- integer, parameter::GEMINI_CODE_INPUT = 10
- integer, parameter::GEMINI_CODE_SUCCESS = 20
- integer, parameter::GEMINI_CODE_REDIRECT = 30
- integer, parameter::GEMINI_CODE_TEMPFAIL = 40
- integer, parameter::GEMINI_CODE_PERMFAIL = 50
+ integer, parameter::GEMINI_CODE_INPUT = 10
+ integer, parameter::GEMINI_CODE_SUCCESS = 20
+ integer, parameter::GEMINI_CODE_REDIRECT = 30
+ integer, parameter::GEMINI_CODE_TEMPFAIL = 40
+ integer, parameter::GEMINI_CODE_PERMFAIL = 50
+ integer, parameter::GEMINI_CODE_BAD_REQUEST = 59
character(*), parameter::RESPONSE_JSON_OKAY = '{"status": "okay"}'
@@ -64,7 +66,10 @@ implicit none
character(len=:), pointer::location => null()
character(len=:), pointer::page => null()
character(len=:), pointer::query_string => null()
+ character(len=:), pointer::token => null()
character(len=4)::method = "GET"
+
+ type(query)::q
contains
@@ -77,14 +82,14 @@ implicit none
procedure :: component => request_component_func
procedure :: is_get => request_is_get
procedure :: is_post => request_is_post
-
+ procedure :: has_query => request_has_query
+
end type request
type, extends(request) :: titan_request
integer(kind=8)::size
character(len=:), pointer::mimetype
- character(len=:), pointer::token
type(c_ptr)::ssl_connection
@@ -197,6 +202,12 @@ contains
call toupper(self%method)
end if
+ if(associated(self%query_string)) then
+ call self%q%init(self%query_string)
+ else
+ call self%q%init()
+ end if
+
end subroutine request_init
function request_component_start_location(self, i_component) result(res)
@@ -365,6 +376,16 @@ contains
end function request_is_post
+ function request_has_query(self)
+ implicit none
+
+ class(request)::self
+ logical::request_has_query
+
+ request_has_query = associated(self%query_string) .and. self%q%component_count() > 0
+
+ end function request_has_query
+
subroutine request_destroy(self)
implicit none
@@ -394,6 +415,8 @@ contains
deallocate(self%page)
end if
+ call self%q%destroy()
+
end subroutine request_destroy
subroutine response_destroy(resp)
diff --git a/captain/security.f90 b/captain/security.f90
index 2f5fa4c..44d40a6 100644
--- a/captain/security.f90
+++ b/captain/security.f90
@@ -104,4 +104,43 @@ contains
end function validate_titan_token
+ ! NOTE: A null() token can be passed, and it might even validate!
+ function validate_query_token(token, player)
+ use captain_db
+ implicit none
+
+ character(len=:), pointer::token
+ character(len=*), intent(in)::player
+
+ logical::validate_query_token
+
+ character(len=:), pointer::dbtoken
+
+ validate_query_token = .false.
+
+ if(associated(token)) then
+ allocate(character(len=len(token))::dbtoken)
+ else
+ allocate(character(len=64)::dbtoken)
+ end if
+
+ dbtoken = ' '
+
+ call get_player_token_db(player, dbtoken)
+
+ ! If no token is provided and none is in the db, then we're okay
+ if((.not. associated(token)) .and. len_trim(dbtoken) == 0) then
+
+ validate_query_token = .true.
+
+ else if(associated(token)) then
+
+ validate_query_token = (trim(token) == trim(dbtoken))
+
+ end if
+
+ deallocate(dbtoken)
+
+ end function validate_query_token
+
end module security \ No newline at end of file
diff --git a/player/endpoints.f90 b/player/endpoints.f90
index 09d3faa..8708592 100644
--- a/player/endpoints.f90
+++ b/player/endpoints.f90
@@ -57,6 +57,27 @@ contains
end subroutine base_url
+ subroutine append_query_token(url)
+ use config, only: token
+ implicit none
+
+ character(len=*), intent(inout)::url
+ character::prepend
+
+ if(len_trim(token) > 0) then
+
+ if(index(url, "?") > 0) then
+ prepend = "&"
+ else
+ prepend = "?"
+ end if
+
+ url = trim(url)//prepend//"token="//trim(token)
+
+ end if
+
+ end subroutine append_query_token
+
subroutine get_check_in_url(res)
use config
use utilities, only: replace_field
@@ -66,6 +87,7 @@ contains
call base_url(captain, LOCATION_CHECK_IN, .false., res)
call replace_field(res, "name", identity)
+ call append_query_token(res)
end subroutine get_check_in_url
@@ -92,9 +114,11 @@ contains
call replace_field(url, "step", step)
if(present(status)) then
- url = trim(url)//"?"//trim(status_text(status))
+ url = trim(url)//"?status="//trim(status_text(status))
end if
-
+
+ call append_query_token(url)
+
end subroutine get_status_url
subroutine get_job_report_url(job, success, res)
@@ -113,6 +137,8 @@ contains
end if
call replace_field(res, "name", identity)
call replace_field(res, "jobid", job)
+
+ call append_query_token(res)
end subroutine get_job_report_url