Skip to content

Commit

Permalink
Merge pull request #2584 from internetee/api-user-management
Browse files Browse the repository at this point in the history
Added ApiUser and WhiteIp endpoints to REPP API
  • Loading branch information
vohmar authored Jun 6, 2023
2 parents 0442f7f + c48d03d commit 4dd355f
Show file tree
Hide file tree
Showing 32 changed files with 934 additions and 27 deletions.
15 changes: 13 additions & 2 deletions app/controllers/repp/v1/accounts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def index
types_for_select: AccountActivity.types_for_select })
end

# rubocop:disable Metrics/MethodLength
api :get, '/repp/v1/accounts/details'
desc 'Get current registrar account details'
def details
Expand All @@ -34,10 +35,15 @@ def details
iban: registrar.iban,
iban_max_length: Iban.max_length,
linked_users: serialized_users(current_user.linked_users),
api_users: serialized_users(current_user.api_users),
white_ips: serialized_ips(registrar.white_ips),
balance_auto_reload: type,
min_deposit: Setting.minimum_deposit } }
min_deposit: Setting.minimum_deposit },
roles: ApiUser::ROLES,
interfaces: WhiteIp::INTERFACES }
render_success(data: resp)
end
# rubocop:enable Metrics/MethodLength

api :put, '/repp/v1/accounts'
desc 'Update current registrar account details'
Expand Down Expand Up @@ -148,7 +154,8 @@ def serialized_users(users)
arr = []
users.each do |u|
arr << { id: u.id, username: u.username,
role: u.roles.first, registrar_name: u.registrar.name }
role: u.roles.first, registrar_name: u.registrar.name,
active: u.active }
end

arr
Expand All @@ -165,6 +172,10 @@ def serialized_activities(activities)

arr
end

def serialized_ips(ips)
ips.as_json(only: %i[id ipv4 ipv6 interfaces])
end
end
end
end
73 changes: 73 additions & 0 deletions app/controllers/repp/v1/api_users_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
require 'serializers/repp/api_user'
module Repp
module V1
class ApiUsersController < BaseController
load_and_authorize_resource

THROTTLED_ACTIONS = %i[index show create update destroy].freeze
include Shunter::Integration::Throttle

api :GET, '/repp/v1/api_users'
desc 'Get all api users'
def index
users = current_user.registrar.api_users

render_success(data: { users: serialized_users(users),
count: users.count })
end

api :GET, '/repp/v1/api_users/:id'
desc 'Get a specific api user'
def show
serializer = Serializers::Repp::ApiUser.new(@api_user)
render_success(data: { user: serializer.to_json, roles: ApiUser::ROLES })
end

api :POST, '/repp/v1/api_users'
desc 'Create a new api user'
def create
@api_user = current_user.registrar.api_users.build(api_user_params)
@api_user.active = api_user_params[:active]
unless @api_user.save
handle_non_epp_errors(@api_user)
return
end

render_success(data: { api_user: { id: @api_user.id } })
end

api :PUT, '/repp/v1/api_users/:id'
desc 'Update api user'
def update
unless @api_user.update(api_user_params)
handle_non_epp_errors(@api_user)
return
end

render_success(data: { api_user: { id: @api_user.id } })
end

api :DELETE, '/repp/v1/api_users/:id'
desc 'Delete a specific api user'
def destroy
unless @api_user.destroy
handle_non_epp_errors(@api_user)
return
end

render_success
end

private

def api_user_params
params.require(:api_user).permit(:username, :plain_text_password, :active,
:identity_code, { roles: [] })
end

def serialized_users(users)
users.map { |i| Serializers::Repp::ApiUser.new(i).to_json }
end
end
end
end
65 changes: 65 additions & 0 deletions app/controllers/repp/v1/white_ips_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
module Repp
module V1
class WhiteIpsController < BaseController
load_and_authorize_resource

THROTTLED_ACTIONS = %i[index show create update destroy].freeze
include Shunter::Integration::Throttle

api :GET, '/repp/v1/white_ips'
desc 'Get all whitelisted IPs'
def index
ips = current_user.registrar.white_ips

render_success(data: { ips: ips.as_json,
count: ips.count })
end

api :GET, '/repp/v1/white_ips/:id'
desc 'Get a specific whitelisted IP address'
def show
render_success(data: { ip: @white_ip.as_json, interfaces: WhiteIp::INTERFACES })
end

api :POST, '/repp/v1/white_ips'
desc 'Add new whitelisted IP'
def create
@white_ip = current_user.registrar.white_ips.build(white_ip_params)
unless @white_ip.save
handle_non_epp_errors(@white_ip)
return
end

render_success(data: { ip: { id: @white_ip.id } })
end

api :PUT, '/repp/v1/white_ips/:id'
desc 'Update whitelisted IP address'
def update
unless @white_ip.update(white_ip_params)
handle_non_epp_errors(@white_ip)
return
end

render_success(data: { ip: { id: @white_ip.id } })
end

api :DELETE, '/repp/v1/white_ips/:id'
desc 'Delete a specific whitelisted IP address'
def destroy
unless @white_ip.destroy
handle_non_epp_errors(@white_ip)
return
end

render_success
end

private

def white_ip_params
params.require(:white_ip).permit(:ipv4, :ipv6, interfaces: [])
end
end
end
end
2 changes: 2 additions & 0 deletions app/models/ability.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ def initialize(user, ip = nil)
def super # Registrar/api_user dynamic role
epp
billing
can :manage, ApiUser
can :manage, WhiteIp
end

def epp # Registrar/api_user dynamic role
Expand Down
17 changes: 16 additions & 1 deletion app/models/api_user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ def self.min_password_length # Must precede .validates
validates :username, :plain_text_password, :registrar, :roles, presence: true
validates :plain_text_password, length: { minimum: min_password_length }
validates :username, uniqueness: true
validates :identity_code, uniqueness: { scope: :registrar_id }, if: -> { identity_code.present? }

delegate :code, :name, to: :registrar, prefix: true
delegate :legaldoc_mandatory?, to: :registrar
Expand All @@ -36,6 +37,8 @@ def self.min_password_length # Must precede .validates

ROLES = %w[super epp billing].freeze # should not match to admin roles

scope :non_super, -> { where.not('roles @> ARRAY[?]::varchar[]', ['super']) }

def ability
@ability ||= Ability.new(self)
end
Expand Down Expand Up @@ -81,12 +84,16 @@ def pki_ok?(crt, com, api: true)
end

def linked_users
self.class.where(identity_code: identity_code)
self.class.where(identity_code: identity_code, active: true)
.where("identity_code IS NOT NULL AND identity_code != ''")
.where.not(id: id)
.includes(:registrar)
end

def api_users
self.class.where(registrar_id: registrar_id)
end

def linked_with?(another_api_user)
another_api_user.identity_code == identity_code
end
Expand All @@ -109,6 +116,14 @@ def self.csv_header
'Accreditation Expire Date', 'Created', 'Updated']
end

def self.ransackable_associations(*)
authorizable_ransackable_associations
end

def self.ransackable_attributes(*)
authorizable_ransackable_attributes
end

private

def machine_readable_certificate(cert)
Expand Down
27 changes: 15 additions & 12 deletions app/models/certificate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,32 @@ class Certificate < ApplicationRecord

belongs_to :api_user

SIGNED = 'signed'
UNSIGNED = 'unsigned'
EXPIRED = 'expired'
REVOKED = 'revoked'
VALID = 'valid'
SIGNED = 'signed'.freeze
UNSIGNED = 'unsigned'.freeze
EXPIRED = 'expired'.freeze
REVOKED = 'revoked'.freeze
VALID = 'valid'.freeze

API = 'api'
REGISTRAR = 'registrar'
INTERFACES = [API, REGISTRAR]
API = 'api'.freeze
REGISTRAR = 'registrar'.freeze
INTERFACES = [API, REGISTRAR].freeze

scope 'api', -> { where(interface: API) }
scope 'registrar', -> { where(interface: REGISTRAR) }

validate :validate_csr_and_crt_presence
def validate_csr_and_crt_presence
return if csr.try(:scrub).present? || crt.try(:scrub).present?

errors.add(:base, I18n.t(:crt_or_csr_must_be_present))
end

validate :validate_csr_and_crt
def validate_csr_and_crt
parsed_crt
parsed_csr
rescue OpenSSL::X509::RequestError, OpenSSL::X509::CertificateError
errors.add(:base, I18n.t(:invalid_csr_or_crt))
rescue OpenSSL::X509::RequestError, OpenSSL::X509::CertificateError
errors.add(:base, I18n.t(:invalid_csr_or_crt))
end

validate :assign_metadata, on: :create
Expand Down Expand Up @@ -67,7 +68,8 @@ def status

@cached_status = SIGNED

@cached_status = EXPIRED if parsed_crt.not_before > Time.zone.now.utc && parsed_crt.not_after < Time.zone.now.utc
expired = parsed_crt.not_before > Time.zone.now.utc && parsed_crt.not_after < Time.zone.now.utc
@cached_status = EXPIRED if expired

crl = OpenSSL::X509::CRL.new(File.open("#{ENV['crl_dir']}/crl.pem").read)
return @cached_status unless crl.revoked.map(&:serial).include?(parsed_crt.serial)
Expand Down Expand Up @@ -144,7 +146,8 @@ def update_crl
end

def parse_md_from_string(crt)
return nil if crt.blank?
return if crt.blank?

crt = crt.split(' ').join("\n")
crt.gsub!("-----BEGIN\nCERTIFICATE-----\n", "-----BEGIN CERTIFICATE-----\n")
crt.gsub!("\n-----END\nCERTIFICATE-----", "\n-----END CERTIFICATE-----")
Expand Down
37 changes: 37 additions & 0 deletions app/models/white_ip.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ class WhiteIp < ApplicationRecord
validate :valid_ipv4?
validate :valid_ipv6?
validate :validate_ipv4_and_ipv6
validate :validate_only_one_ip
validate :validate_max_ip_count
before_save :normalize_blank_values

def normalize_blank_values
Expand All @@ -17,6 +19,12 @@ def validate_ipv4_and_ipv6
errors.add(:base, I18n.t(:ipv4_or_ipv6_must_be_present))
end

def validate_only_one_ip
return unless ipv4.present? && ipv6.present?

errors.add(:base, I18n.t(:ip_must_be_one))
end

def valid_ipv4?
return if ipv4.blank?

Expand All @@ -33,6 +41,31 @@ def valid_ipv6?
errors.add(:ipv6, :invalid)
end

def validate_max_ip_count
return if errors.any?

ip_addresses = registrar.white_ips
total = ip_addresses.size + count_network_addresses(ipv4.presence || ipv6)
limit = Setting.ip_whitelist_max_count
return unless total >= limit

errors.add(:base, I18n.t(:ip_limit_exceeded, total: total, limit: limit))
end

def count_network_addresses(ip)
address = IPAddr.new(ip)

if address.ipv4?
subnet_mask = address.prefix
2**(32 - subnet_mask) - 2
elsif address.ipv6?
subnet_mask = address.prefix
2**(128 - subnet_mask) - 2
else
0
end
end

API = 'api'.freeze
REGISTRAR = 'registrar'.freeze
INTERFACES = [API, REGISTRAR].freeze
Expand Down Expand Up @@ -77,6 +110,10 @@ def check_ip6(ip)
def csv_header
%w[IPv4 IPv6 Interfaces Created Updated]
end

def ransackable_attributes(*)
authorizable_ransackable_attributes
end
end

def as_csv_row
Expand Down
2 changes: 2 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,8 @@ en:
upload_crt: 'Upload CRT'
crt_or_csr_must_be_present: 'CRT or CSR must be present'
ipv4_or_ipv6_must_be_present: 'IPv4 or IPv6 must be present'
ip_must_be_one: 'Please enter only one IP address'
ip_limit_exceeded: 'IP address limit exceeded. Total addresses: %{total}. Limit: %{limit}.'
white_ip: 'White IP'
edit_white_ip: 'Edit white IP'
confirm_domain_delete: 'Confirm domain delete'
Expand Down
2 changes: 2 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@
get '/market_share_growth_rate', to: 'stats#market_share_growth_rate'
end
end
resources :api_users, only: %i[index show update create destroy]
resources :white_ips, only: %i[index show update create destroy]
namespace :registrar do
resources :notifications, only: %i[index show update] do
collection do
Expand Down
Loading

0 comments on commit 4dd355f

Please sign in to comment.