Merge branch 'master' into registry-791

This commit is contained in:
Artur Beljajev 2018-08-21 12:08:45 +03:00
commit ffd389790c
74 changed files with 1646 additions and 137 deletions

View file

@ -1,3 +1,10 @@
26.07.2018
* Grape (1.0.3), mustermann (1.0.2), multi_json (1.13.1) gem updates [#912](https://github.com/internetee/registry/issues/912)
* Capybara (3.3.1), mini_mime (0.1.3), nokogiri (1.8), rack (1.6.0), xpath (3.1) gem updates [#980](https://github.com/internetee/registry/issues/908)
* Webmock (3.4.2), addressable (2.5.2), hashdiff (0.3.7), public_suffix (3.0.2) gem updates [#907](https://github.com/internetee/registry/issues/907)
* fixed typo in assertions filename [#920](https://github.com/internetee/registry/issues/920)
* regenerate structure.sql [#915](https://github.com/internetee/registry/issues/915)
12.07.2018 12.07.2018
* Implemented JavaScript testing framework to catch web UI problems [#900](https://github.com/internetee/registry/issues/900) * Implemented JavaScript testing framework to catch web UI problems [#900](https://github.com/internetee/registry/issues/900)

View file

@ -186,7 +186,7 @@ GEM
thor (~> 0.14) thor (~> 0.14)
globalid (0.4.1) globalid (0.4.1)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
grape (1.0.3) grape (1.1.0)
activesupport activesupport
builder builder
mustermann-grape (~> 1.0.0) mustermann-grape (~> 1.0.0)

View file

@ -0,0 +1,54 @@
require 'rails5_api_controller_backport'
require 'auth_token/auth_token_creator'
module Api
module V1
module Registrant
class AuthController < ActionController::API
before_action :check_ip_whitelist
rescue_from(ActionController::ParameterMissing) do |parameter_missing_exception|
error = {}
error[parameter_missing_exception.param] = ['parameter is required']
response = { errors: [error] }
render json: response, status: :unprocessable_entity
end
def eid
user = RegistrantUser.find_or_create_by_api_data(eid_params)
token = create_token(user)
if token
render json: token
else
render json: { errors: [{ base: ['Cannot create generate session token'] }] }
end
end
private
def eid_params
required_params = %i[ident first_name last_name]
required_params.each_with_object(params) do |key, obj|
obj.require(key)
end
params.permit(required_params)
end
def create_token(user)
token_creator = AuthTokenCreator.create_with_defaults(user)
hash = token_creator.token_in_hash
hash
end
def check_ip_whitelist
allowed_ips = ENV['registrant_api_auth_allowed_ips'].to_s.split(',').map(&:strip)
return if allowed_ips.include?(request.ip) || Rails.env.development?
render json: { errors: [{ base: ['Not authorized'] }] }, status: :unauthorized
end
end
end
end
end

View file

@ -0,0 +1,38 @@
require 'rails5_api_controller_backport'
require 'auth_token/auth_token_decryptor'
module Api
module V1
module Registrant
class BaseController < ActionController::API
before_action :authenticate
rescue_from(ActionController::ParameterMissing) do |parameter_missing_exception|
error = {}
error[parameter_missing_exception.param] = ['parameter is required']
response = { errors: [error] }
render json: response, status: :unprocessable_entity
end
private
def bearer_token
pattern = /^Bearer /
header = request.headers['Authorization']
header.gsub(pattern, '') if header&.match(pattern)
end
def authenticate
decryptor = AuthTokenDecryptor.create_with_defaults(bearer_token)
decryptor.decrypt_token
if decryptor.valid?
sign_in decryptor.user
else
render json: { errors: [{base: ['Not authorized']}] }, status: :unauthorized
end
end
end
end
end
end

View file

@ -0,0 +1,57 @@
module Api
module V1
module Registrant
class ContactsController < BaseController
before_action :set_contacts_pool
def index
limit = params[:limit] || 200
offset = params[:offset] || 0
if limit.to_i > 200 || limit.to_i < 1
render(json: { errors: [{ limit: ['parameter is out of range'] }] },
status: :bad_request) && return
end
if offset.to_i.negative?
render(json: { errors: [{ offset: ['parameter is out of range'] }] },
status: :bad_request) && return
end
@contacts = @contacts_pool.limit(limit).offset(offset)
render json: @contacts
end
def show
@contact = @contacts_pool.find_by(uuid: params[:uuid])
if @contact
render json: @contact
else
render json: { errors: [{ base: ['Contact not found'] }] }, status: :not_found
end
end
private
def set_contacts_pool
country_code, ident = current_user.registrant_ident.to_s.split '-'
associated_domain_ids = begin
BusinessRegistryCache.fetch_by_ident_and_cc(ident, country_code).associated_domain_ids
end
available_contacts_ids = begin
DomainContact.where(domain_id: associated_domain_ids).pluck(:contact_id) |
Domain.where(id: associated_domain_ids).pluck(:registrant_id)
end
@contacts_pool = Contact.where(id: available_contacts_ids)
rescue Soap::Arireg::NotAvailableError => error
Rails.logger.fatal("[EXCEPTION] #{error}")
render json: { errors: [{ base: ['Business Registry not available'] }] },
status: :service_unavailable and return
end
end
end
end
end

View file

@ -0,0 +1,47 @@
module Api
module V1
module Registrant
class DomainsController < BaseController
def index
limit = params[:limit] || 200
offset = params[:offset] || 0
if limit.to_i > 200 || limit.to_i < 1
render(json: { errors: [{ limit: ['parameter is out of range'] }] },
status: :bad_request) && return
end
if offset.to_i.negative?
render(json: { errors: [{ offset: ['parameter is out of range'] }] },
status: :bad_request) && return
end
@domains = associated_domains(current_user).limit(limit).offset(offset)
render json: @domains
end
def show
domain_pool = associated_domains(current_user)
@domain = domain_pool.find_by(uuid: params[:uuid])
if @domain
render json: @domain
else
render json: { errors: [{ base: ['Domain not found'] }] }, status: :not_found
end
end
private
def associated_domains(user)
country_code, ident = user.registrant_ident.split('-')
BusinessRegistryCache.fetch_associated_domains(ident, country_code)
rescue Soap::Arireg::NotAvailableError => error
Rails.logger.fatal("[EXCEPTION] #{error}")
user.domains
end
end
end
end
end

View file

@ -26,4 +26,4 @@ class Registrant::ContactsController < RegistrantController
BusinessRegistryCache.fetch_by_ident_and_cc(ident, ident_cc).associated_domain_ids BusinessRegistryCache.fetch_by_ident_and_cc(ident, ident_cc).associated_domain_ids
end end
end end
end end

View file

@ -10,12 +10,12 @@ class Registrar
@deposit = Deposit.new(deposit_params.merge(registrar: current_user.registrar)) @deposit = Deposit.new(deposit_params.merge(registrar: current_user.registrar))
@invoice = @deposit.issue_prepayment_invoice @invoice = @deposit.issue_prepayment_invoice
if @invoice&.persisted? if @invoice
flash[:notice] = t(:please_pay_the_following_invoice) flash[:notice] = t(:please_pay_the_following_invoice)
redirect_to [:registrar, @invoice] redirect_to [:registrar, @invoice]
else else
flash.now[:alert] = t(:failed_to_create_record) flash[:alert] = @deposit.errors.full_messages.join(', ')
render 'new' redirect_to new_registrar_deposit_path
end end
end end

View file

@ -1,3 +0,0 @@
module DomainVersionObserver
extend ActiveSupport::Concern
end

View file

@ -4,13 +4,17 @@ class Deposit
extend ActiveModel::Naming extend ActiveModel::Naming
include DisableHtml5Validation include DisableHtml5Validation
attr_accessor :amount, :description, :registrar, :registrar_id attr_accessor :description, :registrar, :registrar_id
attr_writer :amount
validates :amount, :registrar, presence: true validates :amount, :registrar, presence: true
validate :validate_amount validate :validate_amount
def validate_amount def validate_amount
return if BigDecimal.new(amount) >= Setting.minimum_deposit minimum_allowed_amount = [0.01, Setting.minimum_deposit].max
errors.add(:amount, I18n.t(:is_too_small_minimum_deposit_is, amount: Setting.minimum_deposit, currency: 'EUR')) return if amount >= minimum_allowed_amount
errors.add(:amount, I18n.t(:is_too_small_minimum_deposit_is, amount: minimum_allowed_amount,
currency: 'EUR'))
end end
def initialize(attributes = {}) def initialize(attributes = {})
@ -24,10 +28,12 @@ class Deposit
end end
def amount def amount
BigDecimal.new(@amount.to_s.sub(/,/, '.')) return BigDecimal('0.0') if @amount.blank?
BigDecimal(@amount.to_s.tr(',', '.'), 10)
end end
def issue_prepayment_invoice def issue_prepayment_invoice
valid? && registrar.issue_prepayment_invoice(amount, description) return unless valid?
registrar.issue_prepayment_invoice(amount, description)
end end
end end

View file

@ -30,34 +30,56 @@ class RegistrantUser < User
return false if issuer_organization != ACCEPTED_ISSUER return false if issuer_organization != ACCEPTED_ISSUER
idc_data.force_encoding('UTF-8') idc_data.force_encoding('UTF-8')
user_data = {}
# handling here new and old mode # handling here new and old mode
if idc_data.starts_with?("/") if idc_data.starts_with?("/")
identity_code = idc_data.scan(/serialNumber=(\d+)/).flatten.first user_data[:ident] = idc_data.scan(/serialNumber=(\d+)/).flatten.first
country = idc_data.scan(/^\/C=(.{2})/).flatten.first user_data[:country_code] = idc_data.scan(/^\/C=(.{2})/).flatten.first
first_name = idc_data.scan(%r{/GN=(.+)/serialNumber}).flatten.first user_data[:first_name] = idc_data.scan(%r{/GN=(.+)/serialNumber}).flatten.first
last_name = idc_data.scan(%r{/SN=(.+)/GN}).flatten.first user_data[:last_name] = idc_data.scan(%r{/SN=(.+)/GN}).flatten.first
else else
parse_str = "," + idc_data parse_str = "," + idc_data
identity_code = parse_str.scan(/,serialNumber=(\d+)/).flatten.first user_data[:ident] = parse_str.scan(/,serialNumber=(\d+)/).flatten.first
country = parse_str.scan(/,C=(.{2})/).flatten.first user_data[:country_code] = parse_str.scan(/,C=(.{2})/).flatten.first
first_name = parse_str.scan(/,GN=([^,]+)/).flatten.first user_data[:first_name] = parse_str.scan(/,GN=([^,]+)/).flatten.first
last_name = parse_str.scan(/,SN=([^,]+)/).flatten.first user_data[:last_name] = parse_str.scan(/,SN=([^,]+)/).flatten.first
end end
u = where(registrant_ident: "#{country}-#{identity_code}").first_or_create find_or_create_by_user_data(user_data)
u.username = "#{first_name} #{last_name}" end
u.save
u def find_or_create_by_api_data(user_data = {})
return false unless user_data[:ident]
return false unless user_data[:first_name]
return false unless user_data[:last_name]
user_data.each_value { |v| v.upcase! if v.is_a?(String) }
user_data[:country_code] ||= 'EE'
find_or_create_by_user_data(user_data)
end end
def find_or_create_by_mid_data(response) def find_or_create_by_mid_data(response)
u = where(registrant_ident: "#{response.user_country}-#{response.user_id_code}").first_or_create user_data = { first_name: response.user_givenname, last_name: response.user_surname,
u.username = "#{response.user_givenname} #{response.user_surname}" ident: response.user_id_code, country_code: response.user_country }
u.save
u find_or_create_by_user_data(user_data)
end
private
def find_or_create_by_user_data(user_data = {})
return unless user_data[:first_name]
return unless user_data[:last_name]
return unless user_data[:ident]
return unless user_data[:country_code]
user = find_or_create_by(registrant_ident: "#{user_data[:country_code]}-#{user_data[:ident]}")
user.username = "#{user_data[:first_name]} #{user_data[:last_name]}"
user.save
user
end end
end end
end end

View file

@ -1,21 +0,0 @@
- content_for :actions do
= render 'shared/title', name: t(:registrars)
.row
.col-md-12
.table-responsive
%table.table.table-hover.table-bordered.table-condensed
%thead
%tr
%th{class: 'col-xs-6'}
= sort_link(@q, 'name')
%th{class: 'col-xs-6'}
= sort_link(@q, 'reg_no', Registrar.human_attribute_name(:reg_no))
%tbody
- @registrars.each do |x|
%tr
%td= link_to(x, [:registrar, x])
%td= x.reg_no
.row
.col-md-12
= paginate @registrars

View file

@ -96,6 +96,8 @@ arireg_host: 'http://demo-ariregxml.rik.ee:81/'
sk_digi_doc_service_endpoint: 'https://tsp.demo.sk.ee' sk_digi_doc_service_endpoint: 'https://tsp.demo.sk.ee'
sk_digi_doc_service_name: 'Testimine' sk_digi_doc_service_name: 'Testimine'
# Registrant API
registrant_api_auth_allowed_ips: '127.0.0.1, 0.0.0.0' #ips, separated with commas
# #
# MISC # MISC

View file

@ -36,7 +36,7 @@ module DomainNameRegistry
config.i18n.default_locale = :en config.i18n.default_locale = :en
config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb') config.paths.add File.join('app', 'api'), glob: File.join('**', '*.rb')
config.autoload_paths += Dir[Rails.root.join('app', 'api', '*')] # config.autoload_paths += Dir[Rails.root.join('app', 'api', '*')]
# Autoload all model subdirs # Autoload all model subdirs
config.autoload_paths += Dir[Rails.root.join('app', 'models', '**/')] config.autoload_paths += Dir[Rails.root.join('app', 'models', '**/')]

View file

@ -18,6 +18,17 @@ Rails.application.routes.draw do
mount Repp::API => '/' mount Repp::API => '/'
namespace :api do
namespace :v1 do
namespace :registrant do
post 'auth/eid', to: 'auth#eid'
resources :domains, only: %i[index show], param: :uuid
resources :contacts, only: %i[index show], param: :uuid
end
end
end
# REGISTRAR ROUTES # REGISTRAR ROUTES
namespace :registrar do namespace :registrar do
resource :dashboard resource :dashboard

11
doc/registrant-api.md Normal file
View file

@ -0,0 +1,11 @@
# Registrant API integration specification
Test API endpoint: TBA
Production API endpoint: TBA
Main communication specification through Registrant API:
[Authentication](registrant-api/v1/authentication.md)
[Domains](registrant-api/v1/domain.md)
[Domain Lock](registrant-api/v1/domain_lock.md)
[Contacts](registrant-api/v1/contact.md)

View file

@ -0,0 +1,109 @@
# Authentication
## Authenticating with mobileID or ID-card
For specified partners the API allows for use of data from mobile ID for
authentication. API client should perform authentication with eID according to
the approriate documentation, and then pass on values from the webserver's
certificate to the API server.
## POST /api/v1/registrant/auth/eid
Returns a bearer token to be used for further API requests. Tokens are valid for 2 hours since their creation.
#### Paramaters
Values in brackets represent values that come from the id card certificate.
| Field name | Required | Type | Allowed values | Description |
| ----------------- | -------- | ---- | -------------- | ----------- |
| ident | true | String | | Identity code of the user (`serialNumber`) |
| first_name | true | String | | Name of the customer (`GN`) |
| last_name | true | String | | Name of the customer (`SN`) |
#### Request
```
POST /api/v1/auth/token HTTP/1.1
Accept: application/json
Content-type: application/json
{
"ident": "30110100103",
"first_name": "Jan",
"last_name": "Tamm",
}
```
#### Response
```
HTTP/1.1 201
Content-Type: application.json
{
"access_token": "<SOME TOKEN>",
"expires_at": "2018-07-13 11:30:51 UTC",
"type": "Bearer"
}
```
## POST /api/v1/auth/username -- NOT IMPLEMENTED
#### Paramaters
Values in brackets represent values that come from the id card certificate
| Field name | Required | Type | Allowed values | Description |
| ----------------- | -------- | ---- | -------------- | ----------- |
| username | true | String | Username as provided by the user | |
| password | true | String | Password as provided by the user | |
#### Request
```
POST /api/v1/auth/token HTTP/1.1
Accept: application/json
Content-type: application/json
```
#### Response
```
HTTP/1.1 201
Content-Type: application.json
{
"access_token": "<SOME TOKEN>",
"expires_at": "2018-07-13 11:30:51 UTC",
"type": "Bearer"
}
```
## Implementation notes:
We do not need to store the session data at all, instead we can levarage AES encryption and use
Rails secret as the key. General approximation:
```ruby
class AuthenticationToken
def initialize(secret = Rails.application.config.secret_key_base, values = {})
end
def create_token_hash
data = values.to_s
cipher = OpenSSL::Cipher::AES.new(256, :CBC)
cipher.encrypt
encrypted = cipher.update(data) + cipher.final
base64_encoded = Base64.encode64(encrypted)
{
token: base64_encoded,
expires_in = values[:expires_in]
type: "Bearer"
}
end
end
```

View file

@ -0,0 +1,194 @@
## GET /api/v1/registrant/contacts
Returns contacts of the current registrar.
#### Parameters
| Field name | Required | Type | Allowed values | Description |
| ---------- | -------- | ---- | -------------- | ----------- |
| limit | false | Integer | [1..200] | How many contacts to show |
| offset | false | Integer | | Contact number to start at |
#### Request
```
GET /api/v1/registrant/contacts?limit=1 HTTP/1.1
Accept: application/json
Authorization: Bearer Z2l0bGFiOmdoeXQ5ZTRmdQ==
Content-Type: application/json
```
#### Response
```
HTTP/1.1 200
Content-Type: application/json
{
"contacts": [
{
"uuid": "84c62f3d-e56f-40fa-9ca4-dc0137778949",
"domain_names": ["example.com"],
"code": "REGISTRAR2:SH022086480",
"phone": "+372.12345678",
"email": "hoyt@deckowbechtelar.net",
"fax": null,
"created_at": "2015-09-09T09:11:14.130Z",
"updated_at": "2015-09-09T09:11:14.130Z",
"ident": "37605030299",
"ident_type": "priv",
"auth_info": "password",
"name": "Karson Kessler0",
"org_name": null,
"registrar_id": 2,
"creator_str": null,
"updator_str": null,
"ident_country_code": "EE",
"city": "Tallinn",
"street": "Short street 11",
"zip": "11111",
"country_code": "EE",
"state": null,
"legacy_id": null,
"statuses": [
"ok"
],
"status_notes": {
}
}
],
"total_number_of_records": 2
}
```
## GET /api/v1/registrant/contacts/$UUID
Returns contacts of the current registrar.
#### Request
```
GET /api/v1/registrant/contacts/84c62f3d-e56f-40fa-9ca4-dc0137778949 HTTP/1.1
Accept: application/json
Authorization: Bearer Z2l0bGFiOmdoeXQ5ZTRmdQ==
Content-Type: application/json
```
#### Response
```
HTTP/1.1 200
Content-Type: application/json
{
"uuid": "84c62f3d-e56f-40fa-9ca4-dc0137778949",
"domain_names": ["example.com"],
"code": "REGISTRAR2:SH022086480",
"phone": "+372.12345678",
"email": "hoyt@deckowbechtelar.net",
"fax": null,
"created_at": "2015-09-09T09:11:14.130Z",
"updated_at": "2015-09-09T09:11:14.130Z",
"ident": "37605030299",
"ident_type": "priv",
"auth_info": "password",
"name": "Karson Kessler0",
"org_name": null,
"registrar_id": 2,
"creator_str": null,
"updator_str": null,
"ident_country_code": "EE",
"city": "Tallinn",
"street": "Short street 11",
"zip": "11111",
"country_code": "EE",
"state": null,
"legacy_id": null,
"statuses": [
"ok"
],
"status_notes": {}
}
```
## PATCH /api/v1/registrant/contacts/$UUID
Update contact details for a contact.
#### Parameters
| Field name | Required | Type | Allowed values | Description |
| ---- | --- | --- | --- | --- |
| email | false | String | | New email address |
| phone | false | String | | New phone number |
| fax | false | String | | New fax number |
| city | false | String | | New city name |
| street | false | String | | New street name |
| zip | false | String | | New zip code |
| country_code | false | String | | New country code in 2 letter format ('EE', 'LV') |
| state | false | String | | New state name |
#### Request
```
PATCH /api/v1/registrant/contacts/84c62f3d-e56f-40fa-9ca4-dc0137778949 HTTP/1.1
Authorization: Bearer Z2l0bGFiOmdoeXQ5ZTRmdQ==
Accept: application/json
Content-type: application/json
{
"email": "foo@bar.baz",
"phone": "+372.12345671",
"fax": "+372.12345672",
"city": "New City",
"street": "Main Street 123",
"zip": "22222",
"country_code": "LV",
"state": "New state"
}
```
#### Response on success
```
HTTP/1.1 200
Content-Type: application.json
{
"uuid": "84c62f3d-e56f-40fa-9ca4-dc0137778949",
"domain_names": ["example.com"],
"code": "REGISTRAR2:SH022086480",
"phone": "+372.12345671",
"email": "foo@bar.baz",
"fax": "+372.12345672",
"created_at": "2015-09-09T09:11:14.130Z",
"updated_at": "2018-09-09T09:11:14.130Z",
"ident": "37605030299",
"ident_type": "priv",
"auth_info": "password",
"name": "Karson Kessler0",
"org_name": null,
"registrar_id": 2,
"creator_str": null,
"updator_str": null,
"ident_country_code": "EE",
"city": "New City",
"street": "Main Street 123",
"zip": "22222",
"country_code": "LV",
"state": "New state"
"legacy_id": null,
"statuses": [
"ok"
],
"status_notes": {}
}
```
### Response on failure
```
HTTP/1.1 400
Content-Type: application.json
{
"errors": [
{ "phone": "Phone nr is invalid" }
]
}
```

View file

@ -0,0 +1,161 @@
# Domain related actions
## GET /api/v1/registrant/domains
Returns domains of the current registrant.
#### Parameters
| Field name | Required | Type | Allowed values | Description |
| ---------- | -------- | ---- | -------------- | ----------- |
| limit | false | Integer | [1..200] | How many domains to show |
| offset | false | Integer | | Domain number to start at |
| details | false | String | ["true", "false"] | Whether to include details |
#### Request
```
GET api/v1/registrant/domains?limit=1&details=true HTTP/1.1
Accept: application/json
Authorization: Bearer Z2l0bGFiOmdoeXQ5ZTRmdQ==
Content-Type: application/json
```
#### Response
```
HTTP/1.1 200
Content-Type: application/json
{
"domains": [
{
"uuid": "98d1083a-8863-4153-93e4-caee4a013535",
"name": "domain0.ee",
"registrar_id": 2,
"registered_at": "2015-09-09T09:11:14.861Z",
"status": null,
"valid_from": "2015-09-09T09:11:14.861Z",
"valid_to": "2016-09-09T09:11:14.861Z",
"registrant_id": 1,
"transfer_code": "98oiewslkfkd",
"created_at": "2015-09-09T09:11:14.861Z",
"updated_at": "2015-09-09T09:11:14.860Z",
"name_dirty": "domain0.ee",
"name_puny": "domain0.ee",
"period": 1,
"period_unit": "y",
"creator_str": null,
"updator_str": null,
"legacy_id": null,
"legacy_registrar_id": null,
"legacy_registrant_id": null,
"outzone_at": "2016-09-24T09:11:14.861Z",
"delete_at": "2016-10-24T09:11:14.861Z",
"registrant_verification_asked_at": null,
"registrant_verification_token": null,
"pending_json": {
},
"force_delete_at": null,
"statuses": [
"ok"
],
"reserved": false,
"status_notes": {
},
"statuses_backup": [
]
}
],
"total_number_of_records": 2
}
```
## GET api/v1/registrant/domains
Returns domain names with offset.
#### Request
```
GET api/v1/registrant/domains?offset=1 HTTP/1.1
Accept: application/json
Authorization: Bearer Z2l0bGFiOmdoeXQ5ZTRmdQ==
Content-Type: application/json
```
#### Response
```
HTTP/1.1 200
Content-Type: application/json
{
"domains": [
"domain1.ee"
],
"total_number_of_records": 2
}
```
## GET api/v1/registrant/domains/$UUID
Returns a single domain object.
#### Request
```
GET api/v1/registrant/domains/98d1083a-8863-4153-93e4-caee4a013535 HTTP/1.1
Accept: application/json
Authorization: Bearer Z2l0bGFiOmdoeXQ5ZTRmdQ==
Content-Type: application/json
```
#### Response for success
```
HTTP/1.1 200
Content-Type: application/json
{
"uuid": "98d1083a-8863-4153-93e4-caee4a013535",
"name": "domain0.ee",
"registrar_id": 2,
"registered_at": "2015-09-09T09:11:14.861Z",
"status": null,
"valid_from": "2015-09-09T09:11:14.861Z",
"valid_to": "2016-09-09T09:11:14.861Z",
"registrant_id": 1,
"transfer_code": "98oiewslkfkd",
"created_at": "2015-09-09T09:11:14.861Z",
"updated_at": "2015-09-09T09:11:14.860Z",
"name_dirty": "domain0.ee",
"name_puny": "domain0.ee",
"period": 1,
"period_unit": "y",
"creator_str": null,
"updator_str": null,
"legacy_id": null,
"legacy_registrar_id": null,
"legacy_registrant_id": null,
"outzone_at": "2016-09-24T09:11:14.861Z",
"delete_at": "2016-10-24T09:11:14.861Z",
"registrant_verification_asked_at": null,
"registrant_verification_token": null,
"pending_json": {},
"force_delete_at": null,
"statuses": [
"ok"
],
"reserved": false,
"status_notes": {},
"statuses_backup": []
}
```
#### Response for failure
```
HTTP/1.1 404
Content-Type: application/json
{ "errors": ["Domain not found"] }
```

View file

@ -0,0 +1,163 @@
# Domain locks
## POST api/v1/registrant/domains/$UUID/registry_lock
Set a registry lock on a domain.
#### Request
```
POST api/v1/registrant/domains/98d1083a-8863-4153-93e4-caee4a013535/registry_lock HTTP/1.1
Accept: application/json
Authorization: Bearer Z2l0bGFiOmdoeXQ5ZTRmdQ==
Content-Type: application/json
```
#### Response for success
```
HTTP/1.1 200
Content-Type: application/json
{
"uuid": "98d1083a-8863-4153-93e4-caee4a013535",
"name": "domain0.ee",
"registrar_id": 2,
"registered_at": "2015-09-09T09:11:14.861Z",
"status": null,
"valid_from": "2015-09-09T09:11:14.861Z",
"valid_to": "2016-09-09T09:11:14.861Z",
"registrant_id": 1,
"transfer_code": "98oiewslkfkd",
"created_at": "2015-09-09T09:11:14.861Z",
"updated_at": "2015-09-09T09:11:14.860Z",
"name_dirty": "domain0.ee",
"name_puny": "domain0.ee",
"period": 1,
"period_unit": "y",
"creator_str": null,
"updator_str": null,
"legacy_id": null,
"legacy_registrar_id": null,
"legacy_registrant_id": null,
"outzone_at": "2016-09-24T09:11:14.861Z",
"delete_at": "2016-10-24T09:11:14.861Z",
"registrant_verification_asked_at": null,
"registrant_verification_token": null,
"pending_json": {},
"force_delete_at": null,
"statuses": [
"serverUpdateProhibited",
"serverDeleteProhibited",
"serverTransferProhibited"
],
"reserved": false,
"status_notes": {},
"statuses_backup": []
}
```
#### Response for failure
```
HTTP/1.1 400
Content-Type: application/json
{
"errors": [
{ "base": "domain cannot be locked" }
]
}
```
```
HTTP/1.1 404
Content-Type: application/json
{
"errors": [
{ "base": "domain does not exist" }
]
}
```
## DELETE api/v1/registrant/domains/$UUID/registry_lock
Remove a registry lock.
#### Request
```
DELETE api/v1/registrant/domains/98d1083a-8863-4153-93e4-caee4a013535/registry_lock HTTP/1.1
Accept: application/json
Authorization: Bearer Z2l0bGFiOmdoeXQ5ZTRmdQ==
Content-Type: application/json
```
#### Response for success
```
HTTP/1.1 200
Content-Type: application/json
{
"uuid": "98d1083a-8863-4153-93e4-caee4a013535",
"name": "domain0.ee",
"registrar_id": 2,
"registered_at": "2015-09-09T09:11:14.861Z",
"status": null,
"valid_from": "2015-09-09T09:11:14.861Z",
"valid_to": "2016-09-09T09:11:14.861Z",
"registrant_id": 1,
"transfer_code": "98oiewslkfkd",
"created_at": "2015-09-09T09:11:14.861Z",
"updated_at": "2015-09-09T09:11:14.860Z",
"name_dirty": "domain0.ee",
"name_puny": "domain0.ee",
"period": 1,
"period_unit": "y",
"creator_str": null,
"updator_str": null,
"legacy_id": null,
"legacy_registrar_id": null,
"legacy_registrant_id": null,
"outzone_at": "2016-09-24T09:11:14.861Z",
"delete_at": "2016-10-24T09:11:14.861Z",
"registrant_verification_asked_at": null,
"registrant_verification_token": null,
"pending_json": {},
"force_delete_at": null,
"statuses": [
"ok"
],
"reserved": false,
"status_notes": {},
"statuses_backup": []
}
```
#### Response for failure
```
HTTP/1.1 400
Content-Type: application/json
{
"errors": [
{ "base": "domain cannot be unlocked" }
]
}
```
```
HTTP/1.1 404
Content-Type: application/json
{
"errors": [
{ "base": "domain does not exist" }
]
}
```

View file

@ -0,0 +1,41 @@
class AuthTokenCreator
DEFAULT_VALIDITY = 2.hours
attr_reader :user
attr_reader :key
attr_reader :expires_at
def self.create_with_defaults(user)
new(user, Rails.application.config.secret_key_base, Time.now + DEFAULT_VALIDITY)
end
def initialize(user, key, expires_at)
@user = user
@key = key
@expires_at = expires_at.utc.strftime('%F %T %Z')
end
def hashable
{
user_ident: user.registrant_ident,
user_username: user.username,
expires_at: expires_at,
}.to_json
end
def encrypted_token
encryptor = OpenSSL::Cipher::AES.new(256, :CBC)
encryptor.encrypt
encryptor.key = key
encrypted_bytes = encryptor.update(hashable) + encryptor.final
Base64.urlsafe_encode64(encrypted_bytes)
end
def token_in_hash
{
access_token: encrypted_token,
expires_at: expires_at,
type: 'Bearer',
}
end
end

View file

@ -0,0 +1,43 @@
class AuthTokenDecryptor
attr_reader :decrypted_data
attr_reader :token
attr_reader :key
attr_reader :user
def self.create_with_defaults(token)
new(token, Rails.application.config.secret_key_base)
end
def initialize(token, key)
@token = token
@key = key
end
def decrypt_token
decipher = OpenSSL::Cipher::AES.new(256, :CBC)
decipher.decrypt
decipher.key = key
base64_decoded = Base64.urlsafe_decode64(token.to_s)
plain = decipher.update(base64_decoded) + decipher.final
@decrypted_data = JSON.parse(plain, symbolize_names: true)
rescue OpenSSL::Cipher::CipherError, ArgumentError
false
end
def valid?
decrypted_data && valid_user? && still_valid?
end
private
def valid_user?
@user = RegistrantUser.find_by(registrant_ident: decrypted_data[:user_ident])
@user&.username == decrypted_data[:user_username]
end
def still_valid?
decrypted_data[:expires_at] > Time.now
end
end

View file

@ -1,39 +0,0 @@
require 'rails_helper'
describe Deposit do
context 'with invalid attribute' do
before :all do
@deposit = Deposit.new
end
it 'should not be valid' do
@deposit.valid?
@deposit.errors.full_messages.should match_array([
"Registrar is missing"
])
end
it 'should have 0 amount' do
@deposit.amount.should == 0
end
it 'should not be presisted' do
@deposit.persisted?.should == false
end
it 'should replace comma with point for 0' do
@deposit.amount = '0,0'
@deposit.amount.should == 0.0
end
it 'should replace comma with points' do
@deposit.amount = '10,11'
@deposit.amount.should == 10.11
end
it 'should work with float as well' do
@deposit.amount = 0.123
@deposit.amount.should == 0.123
end
end
end

View file

@ -3,7 +3,7 @@ require 'test_helper'
require 'database_cleaner' require 'database_cleaner'
require 'selenium/webdriver' require 'selenium/webdriver'
class ApplicationSystemTestCase < ActionDispatch::IntegrationTest; end ApplicationSystemTestCase = Class.new(ApplicationIntegrationTest)
class JavaScriptApplicationSystemTestCase < ApplicationSystemTestCase class JavaScriptApplicationSystemTestCase < ApplicationSystemTestCase
self.use_transactional_fixtures = false self.use_transactional_fixtures = false

View file

@ -42,10 +42,20 @@ metro:
period_unit: m period_unit: m
uuid: ef97cb80-333b-4893-b9df-163f2b452798 uuid: ef97cb80-333b-4893-b9df-163f2b452798
hospital:
name: hospital.test
registrar: goodnames
registrant: john
transfer_code: 23118v2
valid_to: 2010-07-05
period: 1
period_unit: m
uuid: 5edda1a5-3548-41ee-8b65-6d60daf85a37
invalid: invalid:
name: invalid.test name: invalid.test
transfer_code: 1438d6 transfer_code: 1438d6
valid_to: <%= Time.zone.parse('2010-07-05').utc.to_s(:db) %> valid_to: <%= Time.zone.parse('2010-07-05').utc.to_s(:db) %>
registrar: bestnames registrar: bestnames
registrant: invalid registrant: invalid
uuid: 3c430ead-bb17-4b5b-aaa1-caa7dde7e138 uuid: 3c430ead-bb17-4b5b-aaa1-caa7dde7e138

View file

@ -26,3 +26,4 @@ admin:
registrant: registrant:
type: RegistrantUser type: RegistrantUser
registrant_ident: US-1234 registrant_ident: US-1234
username: Registrant User

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class APIDomainContactsTest < ActionDispatch::IntegrationTest class APIDomainContactsTest < ApplicationIntegrationTest
def test_replace_all_tech_contacts_of_the_current_registrar def test_replace_all_tech_contacts_of_the_current_registrar
patch '/repp/v1/domains/contacts', { current_contact_id: 'william-001', patch '/repp/v1/domains/contacts', { current_contact_id: 'william-001',
new_contact_id: 'john-001' }, new_contact_id: 'john-001' },

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class APIDomainTransfersTest < ActionDispatch::IntegrationTest class APIDomainTransfersTest < ApplicationIntegrationTest
setup do setup do
@domain = domains(:shop) @domain = domains(:shop)
@new_registrar = registrars(:goodnames) @new_registrar = registrars(:goodnames)

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class APINameserversPutTest < ActionDispatch::IntegrationTest class APINameserversPutTest < ApplicationIntegrationTest
def test_replaces_registrar_nameservers def test_replaces_registrar_nameservers
old_nameserver_ids = [nameservers(:shop_ns1).id, old_nameserver_ids = [nameservers(:shop_ns1).id,
nameservers(:airport_ns1).id, nameservers(:airport_ns1).id,

View file

@ -0,0 +1,58 @@
require 'test_helper'
class RegistrantApiAuthenticationTest < ApplicationIntegrationTest
def setup
super
@user_hash = { ident: '37010100049', first_name: 'Adam', last_name: 'Baker' }
@existing_user = RegistrantUser.find_or_create_by_api_data(@user_hash)
end
def teardown
super
end
def test_request_creates_user_when_one_does_not_exist
params = {
ident: '30110100103',
first_name: 'John',
last_name: 'Smith',
}
post '/api/v1/registrant/auth/eid', params
assert(User.find_by(registrant_ident: 'EE-30110100103'))
json = JSON.parse(response.body, symbolize_names: true)
assert_equal([:access_token, :expires_at, :type], json.keys)
end
def test_request_returns_existing_user
assert_no_changes User.count do
post '/api/v1/registrant/auth/eid', @user_hash
end
end
def test_request_returns_401_from_a_not_whitelisted_ip
params = { foo: :bar, test: :test }
@original_whitelist_ip = ENV['registrant_api_auth_allowed_ips']
ENV['registrant_api_auth_allowed_ips'] = '1.2.3.4'
post '/api/v1/registrant/auth/eid', params
assert_equal(401, response.status)
json_body = JSON.parse(response.body, symbolize_names: true)
assert_equal({ errors: [base: ['Not authorized']] }, json_body)
ENV['registrant_api_auth_allowed_ips'] = @original_whitelist_ip
end
def test_request_documented_parameters_are_required
params = { foo: :bar, test: :test }
post '/api/v1/registrant/auth/eid', params
json = JSON.parse(response.body, symbolize_names: true)
assert_equal({ errors: [{ ident: ['parameter is required'] }] }, json)
assert_equal(422, response.status)
end
end

View file

@ -0,0 +1,117 @@
require 'test_helper'
require 'auth_token/auth_token_creator'
class RegistrantApiContactsTest < ApplicationIntegrationTest
def setup
super
@original_registry_time = Setting.days_to_keep_business_registry_cache
Setting.days_to_keep_business_registry_cache = 1
travel_to Time.zone.parse('2010-07-05')
@user = users(:registrant)
@auth_headers = { 'HTTP_AUTHORIZATION' => auth_token }
end
def teardown
super
Setting.days_to_keep_business_registry_cache = @original_registry_time
travel_back
end
def test_root_returns_domain_list
get '/api/v1/registrant/contacts', {}, @auth_headers
assert_equal(200, response.status)
json_body = JSON.parse(response.body, symbolize_names: true)
assert_equal(5, json_body.count)
array_of_contact_codes = json_body.map { |x| x[:code] }
assert(array_of_contact_codes.include?('william-001'))
assert(array_of_contact_codes.include?('jane-001'))
end
def test_root_accepts_limit_and_offset_parameters
get '/api/v1/registrant/contacts', { 'limit' => 1, 'offset' => 0 }, @auth_headers
response_json = JSON.parse(response.body, symbolize_names: true)
assert_equal(200, response.status)
assert_equal(1, response_json.count)
get '/api/v1/registrant/contacts', {}, @auth_headers
response_json = JSON.parse(response.body, symbolize_names: true)
assert_equal(5, response_json.count)
end
def test_get_contact_details_by_uuid
get '/api/v1/registrant/contacts/0aa54704-d6f7-4ca9-b8ca-2827d9a4e4eb', {}, @auth_headers
assert_equal(200, response.status)
contact = JSON.parse(response.body, symbolize_names: true)
assert_equal('william@inbox.test', contact[:email])
end
def test_root_returns_503_when_business_registry_is_not_available
raise_not_available = -> (a, b) { raise Soap::Arireg::NotAvailableError.new({}) }
BusinessRegistryCache.stub :fetch_by_ident_and_cc, raise_not_available do
get '/api/v1/registrant/contacts', {}, @auth_headers
assert_equal(503, response.status)
response_json = JSON.parse(response.body, symbolize_names: true)
assert_equal({ errors: [base: ['Business Registry not available']] }, response_json)
end
end
def test_get_contact_details_by_uuid_returns_404_for_non_existent_contact
get '/api/v1/registrant/contacts/nonexistent-uuid', {}, @auth_headers
assert_equal(404, response.status)
response_json = JSON.parse(response.body, symbolize_names: true)
assert_equal({ errors: [{ base: ['Contact not found'] }] }, response_json)
end
def test_root_does_not_accept_limit_higher_than_200
get '/api/v1/registrant/contacts', { 'limit' => 400, 'offset' => 0 }, @auth_headers
assert_equal(400, response.status)
response_json = JSON.parse(response.body, symbolize_names: true)
assert_equal({ errors: [{ limit: ['parameter is out of range'] }] }, response_json)
end
def test_root_does_not_accept_offset_lower_than_0
get '/api/v1/registrant/contacts', { 'limit' => 200, 'offset' => "-10" }, @auth_headers
assert_equal(400, response.status)
response_json = JSON.parse(response.body, symbolize_names: true)
assert_equal({ errors: [{ offset: ['parameter is out of range'] }] }, response_json)
end
def test_root_returns_401_without_authorization
get '/api/v1/registrant/contacts', {}, {}
assert_equal(401, response.status)
json_body = JSON.parse(response.body, symbolize_names: true)
assert_equal({ errors: [base: ['Not authorized']] }, json_body)
end
def test_details_returns_401_without_authorization
get '/api/v1/registrant/contacts/c0a191d5-3793-4f0b-8f85-491612d0293e', {}, {}
assert_equal(401, response.status)
json_body = JSON.parse(response.body, symbolize_names: true)
assert_equal({ errors: [base: ['Not authorized']] }, json_body)
end
def test_details_returns_404_for_non_existent_contact
get '/api/v1/registrant/contacts/some-random-uuid', {}, @auth_headers
assert_equal(404, response.status)
json_body = JSON.parse(response.body, symbolize_names: true)
assert_equal({ errors: [base: ['Contact not found']] }, json_body)
end
private
def auth_token
token_creator = AuthTokenCreator.create_with_defaults(@user)
hash = token_creator.token_in_hash
"Bearer #{hash[:access_token]}"
end
end

View file

@ -0,0 +1,102 @@
require 'test_helper'
require 'auth_token/auth_token_creator'
class RegistrantApiDomainsTest < ApplicationIntegrationTest
def setup
super
@original_registry_time = Setting.days_to_keep_business_registry_cache
Setting.days_to_keep_business_registry_cache = 1
travel_to Time.zone.parse('2010-07-05')
@domain = domains(:hospital)
@registrant = @domain.registrant
@user = users(:registrant)
@auth_headers = { 'HTTP_AUTHORIZATION' => auth_token }
end
def teardown
super
Setting.days_to_keep_business_registry_cache = @original_registry_time
travel_back
end
def test_get_domain_details_by_uuid
get '/api/v1/registrant/domains/5edda1a5-3548-41ee-8b65-6d60daf85a37', {}, @auth_headers
assert_equal(200, response.status)
domain = JSON.parse(response.body, symbolize_names: true)
assert_equal('hospital.test', domain[:name])
end
def test_get_non_existent_domain_details_by_uuid
get '/api/v1/registrant/domains/random-uuid', {}, @auth_headers
assert_equal(404, response.status)
response_json = JSON.parse(response.body, symbolize_names: true)
assert_equal({ errors: [base: ['Domain not found']] }, response_json)
end
def test_root_returns_domain_list
get '/api/v1/registrant/domains', {}, @auth_headers
assert_equal(200, response.status)
response_json = JSON.parse(response.body, symbolize_names: true)
array_of_domain_names = response_json.map { |x| x[:name] }
assert(array_of_domain_names.include?('hospital.test'))
end
def test_root_accepts_limit_and_offset_parameters
get '/api/v1/registrant/domains', { 'limit' => 2, 'offset' => 0 }, @auth_headers
response_json = JSON.parse(response.body, symbolize_names: true)
assert_equal(200, response.status)
assert_equal(2, response_json.count)
get '/api/v1/registrant/domains', {}, @auth_headers
response_json = JSON.parse(response.body, symbolize_names: true)
assert_equal(5, response_json.count)
end
def test_root_does_not_accept_limit_higher_than_200
get '/api/v1/registrant/domains', { 'limit' => 400, 'offset' => 0 }, @auth_headers
assert_equal(400, response.status)
response_json = JSON.parse(response.body, symbolize_names: true)
assert_equal({ errors: [{ limit: ['parameter is out of range'] }] }, response_json)
end
def test_root_does_not_accept_offset_lower_than_0
get '/api/v1/registrant/domains', { 'limit' => 200, 'offset' => "-10" }, @auth_headers
assert_equal(400, response.status)
response_json = JSON.parse(response.body, symbolize_names: true)
assert_equal({ errors: [{ offset: ['parameter is out of range'] }] }, response_json)
end
def test_root_returns_401_without_authorization
get '/api/v1/registrant/domains', {}, {}
assert_equal(401, response.status)
json_body = JSON.parse(response.body, symbolize_names: true)
assert_equal({ errors: [base: ['Not authorized']] }, json_body)
end
def test_details_returns_401_without_authorization
get '/api/v1/registrant/domains/5edda1a5-3548-41ee-8b65-6d60daf85a37', {}, {}
assert_equal(401, response.status)
json_body = JSON.parse(response.body, symbolize_names: true)
assert_equal({ errors: [base: ['Not authorized']] }, json_body)
end
private
def auth_token
token_creator = AuthTokenCreator.create_with_defaults(@user)
hash = token_creator.token_in_hash
"Bearer #{hash[:access_token]}"
end
end

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class EppDomainCreateNameserversTest < ActionDispatch::IntegrationTest class EppDomainCreateNameserversTest < ApplicationIntegrationTest
# Glue record requirement # Glue record requirement
def test_nameserver_ip_address_is_required_if_hostname_is_under_the_same_domain def test_nameserver_ip_address_is_required_if_hostname_is_under_the_same_domain
request_xml = <<-XML request_xml = <<-XML

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class EppDomainCreateTransferCodeTest < ActionDispatch::IntegrationTest class EppDomainCreateTransferCodeTest < ApplicationIntegrationTest
setup do setup do
travel_to Time.zone.parse('2010-07-05') travel_to Time.zone.parse('2010-07-05')
end end

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class EppDomainDeleteTest < ActionDispatch::IntegrationTest class EppDomainDeleteTest < ApplicationIntegrationTest
def test_bypasses_domain_and_registrant_and_contacts_validation def test_bypasses_domain_and_registrant_and_contacts_validation
request_xml = <<-XML request_xml = <<-XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class EppDomainRenewTest < ActionDispatch::IntegrationTest class EppDomainRenewTest < ApplicationIntegrationTest
self.use_transactional_fixtures = false self.use_transactional_fixtures = false
setup do setup do

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class EppDomainUpdateTest < ActionDispatch::IntegrationTest class EppDomainUpdateTest < ApplicationIntegrationTest
def test_update_domain def test_update_domain
request_xml = <<-XML request_xml = <<-XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class EppDomainTransferBaseTest < ActionDispatch::IntegrationTest class EppDomainTransferBaseTest < ApplicationIntegrationTest
def test_non_existent_domain def test_non_existent_domain
request_xml = <<-XML request_xml = <<-XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class EppDomainTransferQueryTest < ActionDispatch::IntegrationTest class EppDomainTransferQueryTest < ApplicationIntegrationTest
def test_returns_domain_transfer_details def test_returns_domain_transfer_details
post '/epp/command/transfer', { frame: request_xml }, { 'HTTP_COOKIE' => 'session=api_bestnames' } post '/epp/command/transfer', { frame: request_xml }, { 'HTTP_COOKIE' => 'session=api_bestnames' }
xml_doc = Nokogiri::XML(response.body) xml_doc = Nokogiri::XML(response.body)

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class EppDomainTransferRequestTest < ActionDispatch::IntegrationTest class EppDomainTransferRequestTest < ApplicationIntegrationTest
setup do setup do
@domain = domains(:shop) @domain = domains(:shop)
@new_registrar = registrars(:goodnames) @new_registrar = registrars(:goodnames)

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class EppLoginCredentialsTest < ActionDispatch::IntegrationTest class EppLoginCredentialsTest < ApplicationIntegrationTest
def test_correct_credentials def test_correct_credentials
request_xml = <<-XML request_xml = <<-XML
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class EppLoginSessionLimitTest < ActionDispatch::IntegrationTest class EppLoginSessionLimitTest < ApplicationIntegrationTest
setup do setup do
travel_to Time.zone.parse('2010-07-05') travel_to Time.zone.parse('2010-07-05')
EppSession.delete_all EppSession.delete_all

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class EppLogoutTest < ActionDispatch::IntegrationTest class EppLogoutTest < ApplicationIntegrationTest
def test_success_response def test_success_response
post '/epp/session/logout', { frame: request_xml }, { 'HTTP_COOKIE' => 'session=api_bestnames' } post '/epp/session/logout', { frame: request_xml }, { 'HTTP_COOKIE' => 'session=api_bestnames' }
assert Nokogiri::XML(response.body).at_css('result[code="1500"]') assert Nokogiri::XML(response.body).at_css('result[code="1500"]')

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class EppPollTest < ActionDispatch::IntegrationTest class EppPollTest < ApplicationIntegrationTest
def test_messages def test_messages
post '/epp/command/poll', { frame: request_xml }, { 'HTTP_COOKIE' => 'session=api_bestnames' } post '/epp/command/poll', { frame: request_xml }, { 'HTTP_COOKIE' => 'session=api_bestnames' }
assert_equal '1301', Nokogiri::XML(response.body).at_css('result')[:code] assert_equal '1301', Nokogiri::XML(response.body).at_css('result')[:code]

View file

@ -0,0 +1,53 @@
require 'test_helper'
require 'openssl'
require_relative '../../../lib/auth_token/auth_token_creator'
class AuthTokenCreatorTest < ActiveSupport::TestCase
def setup
super
@user = users(:registrant)
time = Time.zone.parse('2010-07-05 00:30:00 +0000')
@random_bytes = SecureRandom.random_bytes(64)
@token_creator = AuthTokenCreator.new(@user, @random_bytes, time)
end
def test_hashable_is_constructed_as_expected
expected_hashable = { user_ident: 'US-1234', user_username: 'Registrant User',
expires_at: '2010-07-05 00:30:00 UTC' }.to_json
assert_equal(expected_hashable, @token_creator.hashable)
end
def test_encrypted_token_is_decryptable
encryptor = OpenSSL::Cipher::AES.new(256, :CBC)
encryptor.decrypt
encryptor.key = @random_bytes
base64_decoded = Base64.urlsafe_decode64(@token_creator.encrypted_token)
result = encryptor.update(base64_decoded) + encryptor.final
hashable = { user_ident: 'US-1234', user_username: 'Registrant User',
expires_at: '2010-07-05 00:30:00 UTC' }.to_json
assert_equal(hashable, result)
end
def test_token_in_json_returns_expected_values
@token_creator.stub(:encrypted_token, 'super_secure_token') do
token = @token_creator.token_in_hash
assert_equal('2010-07-05 00:30:00 UTC', token[:expires_at])
assert_equal('Bearer', token[:type])
end
end
def test_create_with_defaults_injects_values
travel_to Time.zone.parse('2010-07-05 00:30:00 +0000')
token_creator_with_defaults = AuthTokenCreator.create_with_defaults(@user)
assert_equal(Rails.application.config.secret_key_base, token_creator_with_defaults.key)
assert_equal('2010-07-05 02:30:00 UTC', token_creator_with_defaults.expires_at)
travel_back
end
end

View file

@ -0,0 +1,82 @@
require 'test_helper'
require_relative '../../../lib/auth_token/auth_token_decryptor'
require_relative '../../../lib/auth_token/auth_token_creator'
class AuthTokenDecryptorTest < ActiveSupport::TestCase
def setup
super
travel_to Time.parse("2010-07-05 00:15:00 UTC")
@user = users(:registrant)
# For testing purposes, the token needs to be random and long enough, hence:
@key = "b8+PtSq1+iXzUVnGEqciKsITNR0KmLl7uPiSTHbteqCoEBdbMLUl3GXlIDWD\nDZp1hIgKWnIMPNEgbuCa/7qccA==\n"
@faulty_key = "FALSE+iXzUVnGEqciKsITNR0KmLl7uPiSTHbteqCoEBdbMLUl3GXlIDWD\nDZp1hIgKWnIMPNEgbuCa/7qccA==\n"
# this token corresponds to:
# {:user_ident=>"US-1234", :user_username=>"Registrant User", :expires_at=>"2010-07-05 02:15:00 UTC"}
@access_token = "q27NWIsKD5snWj9vZzJ0RcOYvgocEyu7H9yCaDjfmGi54sogovpBeALMPWTZHMcdFQzSiq6b4cI0p5tO0_5UEOHic2jRzNW7mkhi-bn-Y2Wlnw7jhMpxw6VwJR8QEoDzjkcNxnKBN6OKF4nssa60ZQ=="
end
def teardown
super
travel_back
end
def test_decrypt_token_returns_a_hash_when_token_is_valid
decryptor = AuthTokenDecryptor.new(@access_token, @key)
assert(decryptor.decrypt_token.is_a?(Hash))
end
def test_decrypt_token_return_false_when_token_is_invalid
faulty_decryptor = AuthTokenDecryptor.new(@access_token, @faulty_key)
refute(faulty_decryptor.decrypt_token)
end
def test_decrypt_token_return_false_when_token_is_nil
faulty_decryptor = AuthTokenDecryptor.new(nil, @key)
refute(faulty_decryptor.decrypt_token)
end
def test_valid_returns_true_for_valid_token
decryptor = AuthTokenDecryptor.new(@access_token, @key)
decryptor.decrypt_token
assert(decryptor.valid?)
end
def test_valid_returns_false_for_invalid_token
faulty_decryptor = AuthTokenDecryptor.new(@access_token, @faulty_key)
faulty_decryptor.decrypt_token
refute(faulty_decryptor.valid?)
end
def test_valid_returns_false_for_expired_token
travel_to Time.parse("2010-07-05 10:15:00 UTC")
decryptor = AuthTokenDecryptor.new(@access_token, @key)
decryptor.decrypt_token
refute(decryptor.valid?)
end
def test_returns_false_for_non_existing_user
# This token was created from an admin user and @key. Decrypted, it corresponds to:
# {:user_ident=>nil, :user_username=>"test", :expires_at=>"2010-07-05 00:15:00 UTC"}
other_token = "rMkjgpyRcj2xOnHVwvvQ5RAS0yQepUSrw3XM5BrwM4TMH-h-TBeLve9InC_zaPneMMnCs0NHQHt1EpH95A2Yhdk6Ge6HQ-4gN5L0THDywCO2vHKGucPxbd6g6wOSaOnR"
decryptor = AuthTokenDecryptor.new(other_token, @key)
decryptor.decrypt_token
refute(decryptor.valid?)
end
def test_create_with_defaults_injects_values
decryptor = AuthTokenDecryptor.create_with_defaults(@access_token)
assert_equal(Rails.application.config.secret_key_base, decryptor.key)
end
end

View file

@ -0,0 +1,37 @@
require 'test_helper'
class ContactTest < ActiveSupport::TestCase
setup do
@contact = contacts(:john)
end
def test_valid_fixture
assert @contact.valid?
end
def test_invalid_without_email
@contact.email = ''
assert @contact.invalid?
end
def test_email_format_validation
@contact.email = 'invalid'
assert @contact.invalid?
@contact.email = 'test@bestmail.test'
assert @contact.valid?
end
def test_invalid_without_phone
@contact.email = ''
assert @contact.invalid?
end
def test_phone_format_validation
@contact.phone = '+123.'
assert @contact.invalid?
@contact.phone = '+123.4'
assert @contact.valid?
end
end

View file

@ -0,0 +1,59 @@
require 'test_helper'
class DepositTest < ActiveSupport::TestCase
def setup
super
@deposit = Deposit.new(registrar: registrars(:bestnames))
@minimum_deposit = Setting.minimum_deposit
Setting.minimum_deposit = 1.00
end
def teardown
super
Setting.minimum_deposit = @minimum_deposit
end
def test_validate_amount_cannot_be_lower_than_0_01
Setting.minimum_deposit = 0.0
@deposit.amount = -10
refute(@deposit.valid?)
assert(@deposit.errors.full_messages.include?("Amount is too small. Minimum deposit is 0.01 EUR"))
end
def test_validate_amount_cannot_be_lower_than_minimum_deposit
@deposit.amount = 0.10
refute(@deposit.valid?)
assert(@deposit.errors.full_messages.include?("Amount is too small. Minimum deposit is 1.0 EUR"))
end
def test_registrar_must_be_set
deposit = Deposit.new(amount: 120)
refute(deposit.valid?)
assert(deposit.errors.full_messages.include?("Registrar is missing"))
end
def test_amount_is_converted_from_string
@deposit.amount = "12.00"
assert_equal(BigDecimal.new("12.00"), @deposit.amount)
@deposit.amount = "12,11"
assert_equal(BigDecimal.new("12.11"), @deposit.amount)
end
def test_amount_is_converted_from_float
@deposit.amount = 12.0044
assert_equal(BigDecimal.new("12.0044"), @deposit.amount)
@deposit.amount = 12.0144
assert_equal(BigDecimal.new("12.0144"), @deposit.amount)
end
def test_amount_is_converted_from_nil
@deposit.amount = nil
assert_equal(BigDecimal.new("0.00"), @deposit.amount)
end
end

View file

@ -0,0 +1,62 @@
class RegistrantUserTest < ActiveSupport::TestCase
def setup
super
end
def teardown
super
end
def test_find_or_create_by_api_data_creates_a_user
user_data = {
ident: '37710100070',
first_name: 'JOHN',
last_name: 'SMITH'
}
RegistrantUser.find_or_create_by_api_data(user_data)
user = User.find_by(registrant_ident: 'EE-37710100070')
assert_equal('JOHN SMITH', user.username)
end
def test_find_or_create_by_api_data_creates_a_user_after_upcasing_input
user_data = {
ident: '37710100070',
first_name: 'John',
last_name: 'Smith'
}
RegistrantUser.find_or_create_by_api_data(user_data)
user = User.find_by(registrant_ident: 'EE-37710100070')
assert_equal('JOHN SMITH', user.username)
end
def test_find_or_create_by_mid_data_creates_a_user
user_data = OpenStruct.new(user_country: 'EE', user_id_code: '37710100070',
user_givenname: 'JOHN', user_surname: 'SMITH')
RegistrantUser.find_or_create_by_mid_data(user_data)
user = User.find_by(registrant_ident: 'EE-37710100070')
assert_equal('JOHN SMITH', user.username)
end
def test_find_or_create_by_idc_with_legacy_header_creates_a_user
header = '/C=EE/O=ESTEID/OU=authentication/CN=SMITH,JOHN,37710100070/SN=SMITH/GN=JOHN/serialNumber=37710100070'
RegistrantUser.find_or_create_by_idc_data(header, RegistrantUser::ACCEPTED_ISSUER)
user = User.find_by(registrant_ident: 'EE-37710100070')
assert_equal('JOHN SMITH', user.username)
end
def test_find_or_create_by_idc_with_rfc2253_header_creates_a_user
header = 'serialNumber=37710100070,GN=JOHN,SN=SMITH,CN=SMITH\\,JOHN\\,37710100070,OU=authentication,O=ESTEID,C=EE'
RegistrantUser.find_or_create_by_idc_data(header, RegistrantUser::ACCEPTED_ISSUER)
user = User.find_by(registrant_ident: 'EE-37710100070')
assert_equal('JOHN SMITH', user.username)
end
end

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class ContactVersionsTest < ActionDispatch::IntegrationTest class ContactVersionsTest < ApplicationSystemTestCase
def setup def setup
super super

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class AdminContactsTest < ActionDispatch::IntegrationTest class AdminContactsTest < ApplicationSystemTestCase
def setup def setup
super super

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class DomainVersionsTest < ActionDispatch::IntegrationTest class DomainVersionsTest < ApplicationSystemTestCase
def setup def setup
super super

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class AdminAreaDomainDetailsTest < ActionDispatch::IntegrationTest class AdminAreaDomainDetailsTest < ApplicationSystemTestCase
setup do setup do
sign_in users(:admin) sign_in users(:admin)
@domain = domains(:shop) @domain = domains(:shop)

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class AdminAreaDomainForceDeleteTest < ActionDispatch::IntegrationTest class AdminAreaDomainForceDeleteTest < ApplicationSystemTestCase
include ActionMailer::TestHelper include ActionMailer::TestHelper
setup do setup do

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class AdminDomainsTestTest < ActionDispatch::IntegrationTest class AdminDomainsTestTest < ApplicationSystemTestCase
setup do setup do
sign_in users(:admin) sign_in users(:admin)
end end

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class AdminAreaNewMailTemplateTest < ActionDispatch::IntegrationTest class AdminAreaNewMailTemplateTest < ApplicationSystemTestCase
setup do setup do
sign_in users(:admin) sign_in users(:admin)
end end

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class AdminAreaDeleteRegistrarTest < ActionDispatch::IntegrationTest class AdminAreaDeleteRegistrarTest < ApplicationSystemTestCase
setup do setup do
sign_in users(:admin) sign_in users(:admin)
end end

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class AdminAreaRegistrarDetailsTest < ActionDispatch::IntegrationTest class AdminAreaRegistrarDetailsTest < ApplicationSystemTestCase
include ActionView::Helpers::NumberHelper include ActionView::Helpers::NumberHelper
setup do setup do

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class AdminAreaEditRegistrarTest < ActionDispatch::IntegrationTest class AdminAreaEditRegistrarTest < ApplicationSystemTestCase
setup do setup do
sign_in users(:admin) sign_in users(:admin)
@registrar = registrars(:bestnames) @registrar = registrars(:bestnames)

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class AdminAreaNewRegistrarTest < ActionDispatch::IntegrationTest class AdminAreaNewRegistrarTest < ApplicationSystemTestCase
setup do setup do
sign_in users(:admin) sign_in users(:admin)
end end

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class RegistrantDomainsTest < ActionDispatch::IntegrationTest class RegistrantDomainsTest < ApplicationSystemTestCase
setup do setup do
sign_in users(:registrant) sign_in users(:registrant)

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class RegistrantLayoutTest < ActionDispatch::IntegrationTest class RegistrantLayoutTest < ApplicationSystemTestCase
def setup def setup
super super
sign_in(users(:registrant)) sign_in(users(:registrant))

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class BalanceTopUpTest < ActionDispatch::IntegrationTest class BalanceTopUpTest < ApplicationSystemTestCase
setup do setup do
sign_in users(:api_bestnames) sign_in users(:api_bestnames)
end end

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class RegistrarAreaBulkTransferTest < ActionDispatch::IntegrationTest class RegistrarAreaBulkTransferTest < ApplicationSystemTestCase
setup do setup do
sign_in users(:api_goodnames) sign_in users(:api_goodnames)
end end

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class RegistrarAreaNameserverBulkChangeTest < ActionDispatch::IntegrationTest class RegistrarAreaNameserverBulkChangeTest < ApplicationSystemTestCase
setup do setup do
sign_in users(:api_goodnames) sign_in users(:api_goodnames)
end end

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class RegistrarAreaTechContactBulkChangeTest < ActionDispatch::IntegrationTest class RegistrarAreaTechContactBulkChangeTest < ApplicationSystemTestCase
setup do setup do
sign_in users(:api_bestnames) sign_in users(:api_bestnames)
end end

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class RegistrarDomainsTest < ActionDispatch::IntegrationTest class RegistrarDomainsTest < ApplicationSystemTestCase
def test_downloads_domain_list_as_csv def test_downloads_domain_list_as_csv
sign_in users(:api_bestnames) sign_in users(:api_bestnames)
travel_to Time.zone.parse('2010-07-05 10:30') travel_to Time.zone.parse('2010-07-05 10:30')

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class ListInvoicesTest < ActionDispatch::IntegrationTest class ListInvoicesTest < ApplicationSystemTestCase
def setup def setup
super super

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class NewInvoicePaymentTest < ActionDispatch::IntegrationTest class NewInvoicePaymentTest < ApplicationSystemTestCase
def setup def setup
super super

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class NewInvoiceTest < ActionDispatch::IntegrationTest class NewInvoiceTest < ApplicationSystemTestCase
def setup def setup
super super
@ -29,11 +29,10 @@ class NewInvoiceTest < ActionDispatch::IntegrationTest
assert_text 'Pay invoice' assert_text 'Pay invoice'
end end
# This test case should fail once issue #651 gets fixed def test_create_new_invoice_with_comma_in_number
def test_create_new_invoice_with_amount_0_goes_through
visit registrar_invoices_path visit registrar_invoices_path
click_link_or_button 'Add deposit' click_link_or_button 'Add deposit'
fill_in 'Amount', with: '0.00' fill_in 'Amount', with: '200,00'
fill_in 'Description', with: 'My first invoice' fill_in 'Description', with: 'My first invoice'
assert_difference 'Invoice.count', 1 do assert_difference 'Invoice.count', 1 do
@ -42,7 +41,33 @@ class NewInvoiceTest < ActionDispatch::IntegrationTest
assert_text 'Please pay the following invoice' assert_text 'Please pay the following invoice'
assert_text 'Invoice no. 131050' assert_text 'Invoice no. 131050'
assert_text 'Subtotal 0,00 €' assert_text 'Subtotal 200,00 €'
assert_text 'Pay invoice' assert_text 'Pay invoice'
end end
def test_create_new_invoice_fails_when_amount_is_0
visit registrar_invoices_path
click_link_or_button 'Add deposit'
fill_in 'Amount', with: '0.00'
fill_in 'Description', with: 'My first invoice'
assert_no_difference 'Invoice.count' do
click_link_or_button 'Add'
end
assert_text 'Amount is too small. Minimum deposit is 0.01 EUR'
end
def test_create_new_invoice_fails_when_amount_is_negative
visit registrar_invoices_path
click_link_or_button 'Add deposit'
fill_in 'Amount', with: '-120.00'
fill_in 'Description', with: 'My first invoice'
assert_no_difference 'Invoice.count' do
click_link_or_button 'Add'
end
assert_text 'Amount is too small. Minimum deposit is 0.01 EUR'
end
end end

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class PaymentCallbackTest < ActionDispatch::IntegrationTest class PaymentCallbackTest < ApplicationSystemTestCase
def setup def setup
super super

View file

@ -1,6 +1,6 @@
require 'test_helper' require 'test_helper'
class PaymentReturnTest < ActionDispatch::IntegrationTest class PaymentReturnTest < ApplicationSystemTestCase
def setup def setup
super super

View file

@ -13,8 +13,6 @@ require 'capybara/minitest'
require 'webmock/minitest' require 'webmock/minitest'
require 'support/rails5_assertions' # Remove once upgraded to Rails 5 require 'support/rails5_assertions' # Remove once upgraded to Rails 5
require 'application_system_test_case'
Setting.address_processing = false Setting.address_processing = false
Setting.registry_country_code = 'US' Setting.registry_country_code = 'US'
@ -29,7 +27,7 @@ class ActiveSupport::TestCase
end end
end end
class ActionDispatch::IntegrationTest class ApplicationIntegrationTest < ActionDispatch::IntegrationTest
include Capybara::DSL include Capybara::DSL
include Capybara::Minitest::Assertions include Capybara::Minitest::Assertions
include AbstractController::Translation include AbstractController::Translation
@ -41,3 +39,5 @@ class ActionDispatch::IntegrationTest
Capybara.use_default_driver Capybara.use_default_driver
end end
end end
require 'application_system_test_case'