258 lines
7.5 KiB
Ruby
258 lines
7.5 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'resolv'
|
|
|
|
class ClientsController < ApplicationController
|
|
before_action :parse_api_key_header, except: %i[show_public_ipv4 create_public_ipv4]
|
|
before_action :authenticate_client, except: %i[show_public_ipv4 create_public_ipv4]
|
|
before_action :authorize_perm_r, only: [:show_ipv4]
|
|
before_action :authorize_perm_w, only: %i[update_ipv4 destroy_ipv4]
|
|
before_action :authorize_perm_x, only: %i[create update index show destroy]
|
|
before_action :authorize_public, only: [:create_public_ipv4] if ENV['RAILS_API_PUBLIC']
|
|
before_action :client_authorized, except: [:show_public_ipv4]
|
|
before_action :demo, only: %i[create update destroy] if ENV['RAILS_API_DEMO']
|
|
|
|
# URL Methods
|
|
# POST /admin/client
|
|
def create
|
|
client_params = params.permit(:name, :description, :permission, :public_ip)
|
|
return head(405) unless client_params.key?(:name)
|
|
|
|
client = Client.find_by name: client_params[:name]
|
|
status_code = client ? 200 : 201
|
|
|
|
# create new client or update existing
|
|
if client.nil?
|
|
client ||= Client.new_client(client_params)
|
|
return head(429) if client.nil? # reached max client count
|
|
else
|
|
# return 406 if request is trying to remove perm_x from last admin account to prevent lockout
|
|
# TODO: move this into the model (update)
|
|
return head(406) if removing_last_admin?(client, client_params)
|
|
|
|
client.update_client(client_params)
|
|
end
|
|
|
|
api_key = client.gen_api_key
|
|
client.save
|
|
json_response({ name: client.name, api_key: api_key }, status_code)
|
|
end
|
|
|
|
# PUT /admin/client
|
|
def update
|
|
client_params = params.permit(:name, :description, :permission, :public_ip)
|
|
return head(405) unless client_params.key?(:name)
|
|
|
|
client = Client.find_by name: client_params[:name]
|
|
return head(404) unless client
|
|
|
|
# return 406 if request is trying to remove perm_x from last admin account to prevent lockout
|
|
# TODO: move this into the model (update)
|
|
return head(406) if removing_last_admin?(client, client_params)
|
|
|
|
# update client
|
|
client.update_client(client_params)
|
|
|
|
client.save
|
|
head(200)
|
|
end
|
|
|
|
# GET /admin/clients
|
|
def index
|
|
clients = []
|
|
Client.find_each do |client|
|
|
clients.append admin_read_object(client)
|
|
end
|
|
json_response(clients)
|
|
end
|
|
|
|
# GET /admin/clients/:name
|
|
def show
|
|
# return 404 if requested client does not exist
|
|
client = Client.find_by name: params[:name]
|
|
return head(404) if client.nil?
|
|
|
|
json_response(admin_read_object(client))
|
|
end
|
|
|
|
# DELETE /admin/clients/:name
|
|
def destroy
|
|
client = Client.find_by(name: params[:name])
|
|
|
|
# return 404 if requested client does not exist
|
|
return head(404) unless client
|
|
|
|
# return 406 if request is trying to remove last admin account to prevent lockout
|
|
# TODO: move this into the model (delete)
|
|
return head(406) if client.admin? && Client.number_of_admins <= 1
|
|
|
|
client.delete
|
|
|
|
head(200)
|
|
end
|
|
|
|
# PUT /client
|
|
def update_ipv4
|
|
ipv4 = request_ip
|
|
|
|
# return 406 if body with invalid ipv4 is present
|
|
# TODO: move this into the model (update)
|
|
return head(406) unless ipv4
|
|
|
|
@requesting_client.ipv4 = ipv4
|
|
@requesting_client.save
|
|
json_response(client_read_object(@requesting_client))
|
|
end
|
|
|
|
# DELETE /client
|
|
def destroy_ipv4
|
|
prev_data = client_read_object(@requesting_client)
|
|
@requesting_client.ipv4 = ''
|
|
@requesting_client.save
|
|
json_response(prev_data)
|
|
end
|
|
|
|
# GET /clients/:id
|
|
def show_ipv4
|
|
# return 404 if requested client does not exist
|
|
client = Client.find_by name: params[:name]
|
|
return head(404) if client.nil?
|
|
|
|
json_response(client_read_object(client))
|
|
end
|
|
|
|
# GET /public/:id
|
|
def show_public_ipv4
|
|
# check if client exist and is public else return 401 (don't leak existence)
|
|
client = Client.find_by name: params[:name]
|
|
return head(401) if client.nil? || !client.public_ip
|
|
|
|
json_response(client_read_object(client))
|
|
end
|
|
|
|
# POST /public/:id
|
|
def create_public_ipv4
|
|
ipv4 = request_ip
|
|
|
|
# return 406 if body with invalid ipv4 is present
|
|
# TODO: move this into the model (update)
|
|
return head(406) unless ipv4
|
|
|
|
client_params = params.permit(:name)
|
|
client = Client.find_by name: params[:name]
|
|
status_code = client ? 200 : 201
|
|
|
|
# create new client or update existing
|
|
if client.nil?
|
|
client ||= Client.new_client(client_params)
|
|
return head(429) if client.nil? # reached max client count
|
|
end
|
|
client.public_ip = true
|
|
client.ipv4 = ipv4
|
|
client.save
|
|
json_response(client_read_object(client), status_code)
|
|
end
|
|
|
|
private
|
|
|
|
# helper methods
|
|
|
|
# respond with json object and status
|
|
def json_response(object, status = :ok)
|
|
render json: object, status: status
|
|
end
|
|
|
|
# checks if request is trying to remove the last admin
|
|
def removing_last_admin?(client, params)
|
|
return false unless params[:permission].is_a?(String)
|
|
|
|
client.admin? && !Client.parse_perm_string(params[:permission])[:x] && Client.number_of_admins <= 1
|
|
end
|
|
|
|
def client_read_object(client)
|
|
{
|
|
name: client.name,
|
|
ipv4: client.ipv4
|
|
}
|
|
end
|
|
|
|
def admin_read_object(client)
|
|
{
|
|
name: client.name,
|
|
description: client.description,
|
|
permission: client.permission,
|
|
public_ip: client.public_ip,
|
|
ipv4: client.ipv4
|
|
}
|
|
end
|
|
|
|
def request_ip
|
|
ipv4_params = params.permit(:ipv4)
|
|
if ipv4_params.key?(:ipv4)
|
|
return nil unless ipv4_params[:ipv4].match(Resolv::IPv4::Regex)
|
|
|
|
ipv4_params[:ipv4]
|
|
else
|
|
request.remote_ip
|
|
end
|
|
end
|
|
|
|
# before_action methods
|
|
|
|
# check if the api is valid and has the appropriate prefix
|
|
def parse_api_key_header
|
|
# TODO: move api_key checks and formatting to helper class
|
|
# check if api key is present and can be split into 3 parts: <prefix>.<name_id>.<api_key>
|
|
# NOTE: .compact is more for got measure than really necessary, but this ensures no nil values in the array
|
|
api_key_header = request.headers[RynDNS::Application::API_KEY_HEADER].split('.').compact unless
|
|
request.headers[RynDNS::Application::API_KEY_HEADER].nil?
|
|
return head(401) if api_key_header.nil? || api_key_header.length < 3
|
|
|
|
prefix, @name_id, @api_key = api_key_header
|
|
# check if prefix matches (do this securely to prevent timing attacks on the prefix)
|
|
return head(403) unless ActiveSupport::SecurityUtils.secure_compare(prefix,
|
|
RynDNS::Application::API_KEY_PREFIX)
|
|
end
|
|
|
|
# authenticate the client with the supplied api key
|
|
def authenticate_client
|
|
# load the request client else return 403
|
|
@requesting_client = Client.find_by name_id: @name_id
|
|
return head(403) unless @requesting_client
|
|
|
|
# compare api_key with hash value else return 403
|
|
return head(403) unless @requesting_client.api_key == @api_key
|
|
end
|
|
|
|
# check if requesting client has read permission
|
|
def authorize_perm_r
|
|
@authorized = @requesting_client.read?
|
|
end
|
|
|
|
# check if requesting client has write permission
|
|
def authorize_perm_w
|
|
@authorized = @requesting_client.write?
|
|
end
|
|
|
|
# check if requesting client has admin permission
|
|
def authorize_perm_x
|
|
@authorized = @requesting_client.admin?
|
|
end
|
|
|
|
def authorize_public
|
|
@authorized = true
|
|
end
|
|
|
|
# check if request is authorized
|
|
def client_authorized
|
|
return head(403) unless @authorized
|
|
end
|
|
|
|
# TODO: put this into a demo environment
|
|
def demo
|
|
demo_clients = %w[admin read_client write_client]
|
|
request_params = params.key?(:name) ? params[:name] : ''
|
|
return head(406) if demo_clients.include? request_params
|
|
end
|
|
end
|