[add] initial commit
This commit is contained in:
220
app/controllers/clients_controller.rb
Normal file
220
app/controllers/clients_controller.rb
Normal file
@@ -0,0 +1,220 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'resolv'
|
||||
|
||||
class ClientsController < ApplicationController
|
||||
before_action :parse_api_key_header, except: [:show_public_ipv4]
|
||||
before_action :authenticate_client, except: [:show_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 :client_authorized, except: [:show_public_ipv4]
|
||||
|
||||
# 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)
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
# check if request is authorized
|
||||
def client_authorized
|
||||
return head(403) unless @authorized
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user