Merge branch 'master' into registry-927

This commit is contained in:
Maciej Szlosarczyk 2018-08-17 11:28:41 +03:00
commit 07072ec6cb
No known key found for this signature in database
GPG key ID: 41D62D42D3B0D765
11 changed files with 741 additions and 52 deletions

View file

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

View file

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

View file

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

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

@ -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

@ -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

@ -29,11 +29,10 @@ class NewInvoiceTest < ApplicationSystemTestCase
assert_text 'Pay invoice'
end
# This test case should fail once issue #651 gets fixed
def test_create_new_invoice_with_amount_0_goes_through
def test_create_new_invoice_with_comma_in_number
visit registrar_invoices_path
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'
assert_difference 'Invoice.count', 1 do
@ -42,7 +41,33 @@ class NewInvoiceTest < ApplicationSystemTestCase
assert_text 'Please pay the following invoice'
assert_text 'Invoice no. 131050'
assert_text 'Subtotal 0,00 €'
assert_text 'Subtotal 200,00 €'
assert_text 'Pay invoice'
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