From 29e06d83c0aa183ea48dfb53bf38162d5d078ab4 Mon Sep 17 00:00:00 2001 From: Maciej Szlosarczyk Date: Thu, 12 Jul 2018 15:13:01 +0300 Subject: [PATCH 01/28] Add description for authentication endpoint --- doc/registrant-api.md | 24 ++++++ doc/registrant-api/authentication.md | 120 +++++++++++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 doc/registrant-api.md create mode 100644 doc/registrant-api/authentication.md diff --git a/doc/registrant-api.md b/doc/registrant-api.md new file mode 100644 index 000000000..c8dd852c2 --- /dev/null +++ b/doc/registrant-api.md @@ -0,0 +1,24 @@ +# Registrant API integration specification + +REPP uses HTTP/1.1 protocol (http://tools.ietf.org/html/rfc2616) and +Basic Authentication (http://tools.ietf.org/html/rfc2617#section-2) using +Secure Transport (https://tools.ietf.org/html/rfc5246) with certificate and key +(https://tools.ietf.org/html/rfc5280). + +Credentials and certificate are issued by EIS (in an exchange for desired API +username, CSR and IP). + +To quickly test the API, use curl: + + curl -q -k --cert user.crt.pem --key user.key.pem https://TBA/repp/v1/accounts/balance -u username:password + +Test API endpoint: https://testepp.internet.ee/repp/v1 +Production API endpoint: TBA + +Main communication specification through Restful EPP (REPP): + +[Contact related functions](repp/v1/contact.md) +[Domain related functions](repp/v1/domain.md) +[Domain transfers](repp/v1/domain_transfers.md) +[Account related functions](repp/v1/account.md) +[Nameservers](repp/v1/nameservers.md) diff --git a/doc/registrant-api/authentication.md b/doc/registrant-api/authentication.md new file mode 100644 index 000000000..5396d82b6 --- /dev/null +++ b/doc/registrant-api/authentication.md @@ -0,0 +1,120 @@ +# 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 /repp/v1/auth/eid/token + +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`) | +| country | true | String | 'ee' | Code of the country that issued the id card (`C`) | +| issuing authority | true | String | 'AS Sertifitseerimiskeskus' | | +| | | | | | + + +#### Request +``` +POST /repp/v1/auth/token HTTP/1.1 +Accept: application/json +Content-length: 0 +Content-type: application/json + +{ + "ident": "30110100103", + "first_name": "Jaan", + "last_name": "Tamm", + "country": "ee", + "issuing authority": "AS Sertifitseerimiskeskus" +} +``` + +#### Response +``` +HTTP/1.1 201 +Cache-Control: max-age=0, private, must-revalidate +Content-Length: 0 +Content-Type: application.json + + +{ + "access_token": "", + "expires_at": "2018-07-13 11:30:51 UTC", + "type": "Bearer" +} +``` + +## POST /repp/v1/auth/username/token -- 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 /repp/v1/auth/token HTTP/1.1 +Accept: application/json +Content-length: 0 +Content-type: application/json +``` + +#### Response +``` +HTTP/1.1 201 +Cache-Control: max-age=0, private, must-revalidate +Content-Length: 0 +Content-Type: application.json + + +{ + "access_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 +``` From d49b4b5c0f622665577cef2100402cc40cc0fc9e Mon Sep 17 00:00:00 2001 From: Maciej Szlosarczyk Date: Fri, 13 Jul 2018 16:19:34 +0300 Subject: [PATCH 02/28] Add domain list documentation --- doc/registrant-api.md | 8 +- doc/registrant-api/{ => v1}/authentication.md | 2 +- doc/registrant-api/v1/domain.md | 102 ++++++++++++++++++ 3 files changed, 106 insertions(+), 6 deletions(-) rename doc/registrant-api/{ => v1}/authentication.md (98%) create mode 100644 doc/registrant-api/v1/domain.md diff --git a/doc/registrant-api.md b/doc/registrant-api.md index c8dd852c2..7ab295d38 100644 --- a/doc/registrant-api.md +++ b/doc/registrant-api.md @@ -17,8 +17,6 @@ Production API endpoint: TBA Main communication specification through Restful EPP (REPP): -[Contact related functions](repp/v1/contact.md) -[Domain related functions](repp/v1/domain.md) -[Domain transfers](repp/v1/domain_transfers.md) -[Account related functions](repp/v1/account.md) -[Nameservers](repp/v1/nameservers.md) +[Authentication](registrant-api/v1/authentication.md) +[Domain related functions](registrant-api/v1/domain.md) +[Contact related functions](registrant-api/v1/contact.md) diff --git a/doc/registrant-api/authentication.md b/doc/registrant-api/v1/authentication.md similarity index 98% rename from doc/registrant-api/authentication.md rename to doc/registrant-api/v1/authentication.md index 5396d82b6..f9486f57a 100644 --- a/doc/registrant-api/authentication.md +++ b/doc/registrant-api/v1/authentication.md @@ -7,7 +7,7 @@ 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 /repp/v1/auth/eid/token +## POST /repp/v1/registrant/auth/eid/token Returns a bearer token to be used for further API requests. Tokens are valid for 2 hours since their creation. diff --git a/doc/registrant-api/v1/domain.md b/doc/registrant-api/v1/domain.md new file mode 100644 index 000000000..b39f28cae --- /dev/null +++ b/doc/registrant-api/v1/domain.md @@ -0,0 +1,102 @@ +# Domain related actions + +## GET /repp/v1/registrant/domains +Returns domains of the current registrar. + + +#### 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 repp/v1/registrant/domains?limit=1&details=true HTTP/1.1 +Accept: application/json +Authorization: Bearer Z2l0bGFiOmdoeXQ5ZTRmdQ== +Content-Length: 0 +Content-Type: application/json +``` + +#### Response +``` +HTTP/1.1 200 +Cache-Control: max-age=0, private, must-revalidate +Content-Length: 808 +Content-Type: application/json + +{ + "domains": [ + { + "id": 1, + "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 repp/v1/registrant/domains +Returns domain names with offset. + + +#### Request +``` +GET repp/v1/registrant/domains?offset=1 HTTP/1.1 +Accept: application/json +Authorization: Bearer Z2l0bGFiOmdoeXQ5ZTRmdQ== +Content-Length: 0 +Content-Type: application/json +``` + +#### Response +``` +HTTP/1.1 200 +Cache-Control: max-age=0, private, must-revalidate +Content-Length: 54 +Content-Type: application/json + +{ + "domains": [ + "domain1.ee" + ], + "total_number_of_records": 2 +} +``` From f965878b42a327904d1e76143419abd9c85c34a4 Mon Sep 17 00:00:00 2001 From: Maciej Szlosarczyk Date: Mon, 16 Jul 2018 10:28:17 +0300 Subject: [PATCH 03/28] Add contact endpoint documentation --- doc/registrant-api/v1/authentication.md | 6 +- doc/registrant-api/v1/contact.md | 148 ++++++++++++++++++++++++ doc/registrant-api/v1/domain.md | 8 +- 3 files changed, 157 insertions(+), 5 deletions(-) create mode 100644 doc/registrant-api/v1/contact.md diff --git a/doc/registrant-api/v1/authentication.md b/doc/registrant-api/v1/authentication.md index f9486f57a..f914a7a23 100644 --- a/doc/registrant-api/v1/authentication.md +++ b/doc/registrant-api/v1/authentication.md @@ -7,7 +7,7 @@ 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 /repp/v1/registrant/auth/eid/token +## POST /repp/v1/registrant/auth/eid Returns a bearer token to be used for further API requests. Tokens are valid for 2 hours since their creation. @@ -34,7 +34,7 @@ Content-type: application/json { "ident": "30110100103", - "first_name": "Jaan", + "first_name": "Jan", "last_name": "Tamm", "country": "ee", "issuing authority": "AS Sertifitseerimiskeskus" @@ -56,7 +56,7 @@ Content-Type: application.json } ``` -## POST /repp/v1/auth/username/token -- NOT IMPLEMENTED +## POST /repp/v1/auth/username -- NOT IMPLEMENTED #### Paramaters diff --git a/doc/registrant-api/v1/contact.md b/doc/registrant-api/v1/contact.md new file mode 100644 index 000000000..126ae27c6 --- /dev/null +++ b/doc/registrant-api/v1/contact.md @@ -0,0 +1,148 @@ +## GET /repp/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 /repp/v1/registrant/contacts?limit=1 HTTP/1.1 +Accept: application/json +Authorization: Bearer Z2l0bGFiOmdoeXQ5ZTRmdQ== +Content-Type: application/json +``` + +#### Response +``` +HTTP/1.1 200 +Cache-Control: max-age=0, private, must-revalidate +Content-Length: 564 +Content-Type: application/json + +{ + "contacts": [ + { + "uuid": "84c62f3d-e56f-40fa-9ca4-dc0137778949", + "domain_name": "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 +} +``` + +## PUT/PATCH /rep/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 +``` +PUT /repp/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_name": "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" } + ] +} +``` diff --git a/doc/registrant-api/v1/domain.md b/doc/registrant-api/v1/domain.md index b39f28cae..5ea82a376 100644 --- a/doc/registrant-api/v1/domain.md +++ b/doc/registrant-api/v1/domain.md @@ -1,7 +1,7 @@ # Domain related actions ## GET /repp/v1/registrant/domains -Returns domains of the current registrar. +Returns domains of the current registrant. #### Parameters @@ -65,7 +65,6 @@ Content-Type: application/json "status_notes": { }, "statuses_backup": [ - ] } ], @@ -100,3 +99,8 @@ Content-Type: application/json "total_number_of_records": 2 } ``` + +#### Implementation details + +This endpoint is practically a copy-paste from similar endpoint used by +registrars. From 2e2e5cd08deb698b9c0cc130d74c531969bcc0d0 Mon Sep 17 00:00:00 2001 From: Maciej Szlosarczyk Date: Mon, 16 Jul 2018 13:32:40 +0300 Subject: [PATCH 04/28] Add GET contact info endpoint --- doc/registrant-api/v1/contact.md | 52 +++++++++++++++++++++++++++++++- doc/registrant-api/v1/domain.md | 7 +---- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/doc/registrant-api/v1/contact.md b/doc/registrant-api/v1/contact.md index 126ae27c6..127c9fdb4 100644 --- a/doc/registrant-api/v1/contact.md +++ b/doc/registrant-api/v1/contact.md @@ -61,7 +61,57 @@ Content-Type: application/json } ``` -## PUT/PATCH /rep/v1/registrant/contacts/$UUID +## GET /repp/v1/registrant/contacts/$UUID +Returns contacts of the current registrar. + + +#### Request +``` +GET /repp/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 +Cache-Control: max-age=0, private, must-revalidate +Content-Length: 564 +Content-Type: application/json + +{ + "uuid": "84c62f3d-e56f-40fa-9ca4-dc0137778949", + "domain_name": "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": {} +} +``` + +## PUT/PATCH /repp/v1/registrant/contacts/$UUID Update contact details for a contact. diff --git a/doc/registrant-api/v1/domain.md b/doc/registrant-api/v1/domain.md index 5ea82a376..db9b17978 100644 --- a/doc/registrant-api/v1/domain.md +++ b/doc/registrant-api/v1/domain.md @@ -31,7 +31,7 @@ Content-Type: application/json { "domains": [ { - "id": 1, + "uuid": "98d1083a-8863-4153-93e4-caee4a013535", "name": "domain0.ee", "registrar_id": 2, "registered_at": "2015-09-09T09:11:14.861Z", @@ -99,8 +99,3 @@ Content-Type: application/json "total_number_of_records": 2 } ``` - -#### Implementation details - -This endpoint is practically a copy-paste from similar endpoint used by -registrars. From 6aeda6444e9efcaecfc14a62347ae557f570bc23 Mon Sep 17 00:00:00 2001 From: Maciej Szlosarczyk Date: Mon, 16 Jul 2018 13:47:55 +0300 Subject: [PATCH 05/28] Change `domain_names` to be an array, not a singular object --- doc/registrant-api/v1/contact.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/registrant-api/v1/contact.md b/doc/registrant-api/v1/contact.md index 127c9fdb4..32d194a75 100644 --- a/doc/registrant-api/v1/contact.md +++ b/doc/registrant-api/v1/contact.md @@ -28,7 +28,7 @@ Content-Type: application/json "contacts": [ { "uuid": "84c62f3d-e56f-40fa-9ca4-dc0137778949", - "domain_name": "example.com" + "domain_names": ["example.com"], "code": "REGISTRAR2:SH022086480", "phone": "+372.12345678", "email": "hoyt@deckowbechtelar.net", @@ -82,7 +82,7 @@ Content-Type: application/json { "uuid": "84c62f3d-e56f-40fa-9ca4-dc0137778949", - "domain_name": "example.com" + "domain_names": ["example.com"], "code": "REGISTRAR2:SH022086480", "phone": "+372.12345678", "email": "hoyt@deckowbechtelar.net", @@ -156,7 +156,7 @@ Content-Type: application.json { "uuid": "84c62f3d-e56f-40fa-9ca4-dc0137778949", - "domain_name": "example.com" + "domain_names": ["example.com"], "code": "REGISTRAR2:SH022086480", "phone": "+372.12345671", "email": "foo@bar.baz", From e2d2768b6e35de19ed96e45f61eb5f8c693888ee Mon Sep 17 00:00:00 2001 From: Maciej Szlosarczyk Date: Mon, 16 Jul 2018 14:26:59 +0300 Subject: [PATCH 06/28] Update domain endpoint --- doc/registrant-api/v1/domain.md | 68 +++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 4 deletions(-) diff --git a/doc/registrant-api/v1/domain.md b/doc/registrant-api/v1/domain.md index db9b17978..bfc4349eb 100644 --- a/doc/registrant-api/v1/domain.md +++ b/doc/registrant-api/v1/domain.md @@ -17,7 +17,6 @@ Returns domains of the current registrant. GET repp/v1/registrant/domains?limit=1&details=true HTTP/1.1 Accept: application/json Authorization: Bearer Z2l0bGFiOmdoeXQ5ZTRmdQ== -Content-Length: 0 Content-Type: application/json ``` @@ -81,15 +80,12 @@ Returns domain names with offset. GET repp/v1/registrant/domains?offset=1 HTTP/1.1 Accept: application/json Authorization: Bearer Z2l0bGFiOmdoeXQ5ZTRmdQ== -Content-Length: 0 Content-Type: application/json ``` #### Response ``` HTTP/1.1 200 -Cache-Control: max-age=0, private, must-revalidate -Content-Length: 54 Content-Type: application/json { @@ -99,3 +95,67 @@ Content-Type: application/json "total_number_of_records": 2 } ``` + +## GET repp/v1/registrant/domains/$UUID +Returns domain names with offset. + + +#### Request +``` +GET repp/v1/registrant/domains/98d1083a-8863-4153-93e4-caee4a013535 HTTP/1.1 +Accept: application/json +Authorization: Bearer Z2l0bGFiOmdoeXQ5ZTRmdQ== +Content-Length: 0 +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 + +{ "error": "Domain not found" } +``` From 270444b2e8be80dd81b2781890c1715f8d2c75f8 Mon Sep 17 00:00:00 2001 From: Maciej Szlosarczyk Date: Mon, 16 Jul 2018 16:03:51 +0300 Subject: [PATCH 07/28] Add registry lock documentation --- doc/registrant-api.md | 21 +--- doc/registrant-api/v1/domain.md | 5 +- doc/registrant-api/v1/domain_lock.md | 164 +++++++++++++++++++++++++++ 3 files changed, 173 insertions(+), 17 deletions(-) create mode 100644 doc/registrant-api/v1/domain_lock.md diff --git a/doc/registrant-api.md b/doc/registrant-api.md index 7ab295d38..c6f063a91 100644 --- a/doc/registrant-api.md +++ b/doc/registrant-api.md @@ -1,22 +1,11 @@ # Registrant API integration specification -REPP uses HTTP/1.1 protocol (http://tools.ietf.org/html/rfc2616) and -Basic Authentication (http://tools.ietf.org/html/rfc2617#section-2) using -Secure Transport (https://tools.ietf.org/html/rfc5246) with certificate and key -(https://tools.ietf.org/html/rfc5280). - -Credentials and certificate are issued by EIS (in an exchange for desired API -username, CSR and IP). - -To quickly test the API, use curl: - - curl -q -k --cert user.crt.pem --key user.key.pem https://TBA/repp/v1/accounts/balance -u username:password - -Test API endpoint: https://testepp.internet.ee/repp/v1 +Test API endpoint: TBA Production API endpoint: TBA -Main communication specification through Restful EPP (REPP): +Main communication specification through Registrant API: [Authentication](registrant-api/v1/authentication.md) -[Domain related functions](registrant-api/v1/domain.md) -[Contact related functions](registrant-api/v1/contact.md) +[Domains](registrant-api/v1/domain.md) +[Domain Lock](registrant-api/v1/domain_lock.md) +[Contacts](registrant-api/v1/contact.md) diff --git a/doc/registrant-api/v1/domain.md b/doc/registrant-api/v1/domain.md index bfc4349eb..f4b5b2895 100644 --- a/doc/registrant-api/v1/domain.md +++ b/doc/registrant-api/v1/domain.md @@ -1,6 +1,7 @@ # Domain related actions ## GET /repp/v1/registrant/domains + Returns domains of the current registrant. @@ -72,6 +73,7 @@ Content-Type: application/json ``` ## GET repp/v1/registrant/domains + Returns domain names with offset. @@ -97,7 +99,8 @@ Content-Type: application/json ``` ## GET repp/v1/registrant/domains/$UUID -Returns domain names with offset. + +Returns a single domain object. #### Request diff --git a/doc/registrant-api/v1/domain_lock.md b/doc/registrant-api/v1/domain_lock.md new file mode 100644 index 000000000..7dcd6efa4 --- /dev/null +++ b/doc/registrant-api/v1/domain_lock.md @@ -0,0 +1,164 @@ +# Domain locks + +## POST repp/v1/registrant/domains/$UUID/registry_lock + +Set a registry lock on a domain. + +#### Request +``` +POST repp/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": [ + "clientUpdateProhibited", + "serverUpdateProhibited", + "serverTransferProhibited", + "serverDeleteProhibited", + ], + "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 repp/v1/registrant/domains/$UUID/registry_lock + +Remove a registry lock. + +#### Request +``` +DELETE repp/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" } + ] +} + +``` From e42951bec881fb2a5bb47c0347e6fcc9bc0020fc Mon Sep 17 00:00:00 2001 From: Maciej Szlosarczyk Date: Tue, 17 Jul 2018 12:46:21 +0300 Subject: [PATCH 08/28] Update statuses for registry lock --- doc/registrant-api/v1/domain_lock.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/registrant-api/v1/domain_lock.md b/doc/registrant-api/v1/domain_lock.md index 7dcd6efa4..6702d4233 100644 --- a/doc/registrant-api/v1/domain_lock.md +++ b/doc/registrant-api/v1/domain_lock.md @@ -47,9 +47,8 @@ Content-Type: application/json "force_delete_at": null, "statuses": [ "clientUpdateProhibited", - "serverUpdateProhibited", - "serverTransferProhibited", - "serverDeleteProhibited", + "clientDeleteProhibited", + "clientTransferProhibited" ], "reserved": false, "status_notes": {}, From d71726c55d1ee23741272de63a273704ea63a04a Mon Sep 17 00:00:00 2001 From: Maciej Szlosarczyk Date: Tue, 17 Jul 2018 15:04:55 +0300 Subject: [PATCH 09/28] Remove Content-Length header from examples --- doc/registrant-api/v1/authentication.md | 4 ---- doc/registrant-api/v1/contact.md | 2 -- doc/registrant-api/v1/domain.md | 2 -- 3 files changed, 8 deletions(-) diff --git a/doc/registrant-api/v1/authentication.md b/doc/registrant-api/v1/authentication.md index f914a7a23..b3b926a67 100644 --- a/doc/registrant-api/v1/authentication.md +++ b/doc/registrant-api/v1/authentication.md @@ -29,7 +29,6 @@ Values in brackets represent values that come from the id card certificate. ``` POST /repp/v1/auth/token HTTP/1.1 Accept: application/json -Content-length: 0 Content-type: application/json { @@ -45,7 +44,6 @@ Content-type: application/json ``` HTTP/1.1 201 Cache-Control: max-age=0, private, must-revalidate -Content-Length: 0 Content-Type: application.json @@ -72,7 +70,6 @@ Values in brackets represent values that come from the id card certificate ``` POST /repp/v1/auth/token HTTP/1.1 Accept: application/json -Content-length: 0 Content-type: application/json ``` @@ -80,7 +77,6 @@ Content-type: application/json ``` HTTP/1.1 201 Cache-Control: max-age=0, private, must-revalidate -Content-Length: 0 Content-Type: application.json diff --git a/doc/registrant-api/v1/contact.md b/doc/registrant-api/v1/contact.md index 32d194a75..8ce129cdb 100644 --- a/doc/registrant-api/v1/contact.md +++ b/doc/registrant-api/v1/contact.md @@ -21,7 +21,6 @@ Content-Type: application/json ``` HTTP/1.1 200 Cache-Control: max-age=0, private, must-revalidate -Content-Length: 564 Content-Type: application/json { @@ -77,7 +76,6 @@ Content-Type: application/json ``` HTTP/1.1 200 Cache-Control: max-age=0, private, must-revalidate -Content-Length: 564 Content-Type: application/json { diff --git a/doc/registrant-api/v1/domain.md b/doc/registrant-api/v1/domain.md index f4b5b2895..49953f4a1 100644 --- a/doc/registrant-api/v1/domain.md +++ b/doc/registrant-api/v1/domain.md @@ -25,7 +25,6 @@ Content-Type: application/json ``` HTTP/1.1 200 Cache-Control: max-age=0, private, must-revalidate -Content-Length: 808 Content-Type: application/json { @@ -108,7 +107,6 @@ Returns a single domain object. GET repp/v1/registrant/domains/98d1083a-8863-4153-93e4-caee4a013535 HTTP/1.1 Accept: application/json Authorization: Bearer Z2l0bGFiOmdoeXQ5ZTRmdQ== -Content-Length: 0 Content-Type: application/json ``` From aa87d001d562ebf84ca0943653ac533a84ee69f5 Mon Sep 17 00:00:00 2001 From: Maciej Szlosarczyk Date: Tue, 17 Jul 2018 15:07:32 +0300 Subject: [PATCH 10/28] Remove Cache-Control headers from examples --- doc/registrant-api/v1/authentication.md | 2 -- doc/registrant-api/v1/contact.md | 2 -- doc/registrant-api/v1/domain.md | 1 - 3 files changed, 5 deletions(-) diff --git a/doc/registrant-api/v1/authentication.md b/doc/registrant-api/v1/authentication.md index b3b926a67..1c65691d2 100644 --- a/doc/registrant-api/v1/authentication.md +++ b/doc/registrant-api/v1/authentication.md @@ -43,7 +43,6 @@ Content-type: application/json #### Response ``` HTTP/1.1 201 -Cache-Control: max-age=0, private, must-revalidate Content-Type: application.json @@ -76,7 +75,6 @@ Content-type: application/json #### Response ``` HTTP/1.1 201 -Cache-Control: max-age=0, private, must-revalidate Content-Type: application.json diff --git a/doc/registrant-api/v1/contact.md b/doc/registrant-api/v1/contact.md index 8ce129cdb..ea28294d0 100644 --- a/doc/registrant-api/v1/contact.md +++ b/doc/registrant-api/v1/contact.md @@ -20,7 +20,6 @@ Content-Type: application/json #### Response ``` HTTP/1.1 200 -Cache-Control: max-age=0, private, must-revalidate Content-Type: application/json { @@ -75,7 +74,6 @@ Content-Type: application/json #### Response ``` HTTP/1.1 200 -Cache-Control: max-age=0, private, must-revalidate Content-Type: application/json { diff --git a/doc/registrant-api/v1/domain.md b/doc/registrant-api/v1/domain.md index 49953f4a1..80290fbac 100644 --- a/doc/registrant-api/v1/domain.md +++ b/doc/registrant-api/v1/domain.md @@ -24,7 +24,6 @@ Content-Type: application/json #### Response ``` HTTP/1.1 200 -Cache-Control: max-age=0, private, must-revalidate Content-Type: application/json { From e1605f81eb75278a91ffe16df9bbf8003cd872ab Mon Sep 17 00:00:00 2001 From: Maciej Szlosarczyk Date: Wed, 18 Jul 2018 14:34:07 +0300 Subject: [PATCH 11/28] Fix error in authentication field name --- doc/registrant-api/v1/authentication.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/registrant-api/v1/authentication.md b/doc/registrant-api/v1/authentication.md index 1c65691d2..2b25a62c2 100644 --- a/doc/registrant-api/v1/authentication.md +++ b/doc/registrant-api/v1/authentication.md @@ -21,7 +21,7 @@ Values in brackets represent values that come from the id card certificate. | first_name | true | String | | Name of the customer (`GN`) | | last_name | true | String | | Name of the customer (`SN`) | | country | true | String | 'ee' | Code of the country that issued the id card (`C`) | -| issuing authority | true | String | 'AS Sertifitseerimiskeskus' | | +| issuing_authority | true | String | 'AS Sertifitseerimiskeskus' | | | | | | | | @@ -36,7 +36,7 @@ Content-type: application/json "first_name": "Jan", "last_name": "Tamm", "country": "ee", - "issuing authority": "AS Sertifitseerimiskeskus" + "issuing_authority": "AS Sertifitseerimiskeskus" } ``` From 74c97eb9238c7c187495f39ea68d12e4049d37ee Mon Sep 17 00:00:00 2001 From: Maciej Szlosarczyk Date: Wed, 18 Jul 2018 15:21:53 +0300 Subject: [PATCH 12/28] Remove issuing authority as a parameter from authentication --- doc/registrant-api/v1/authentication.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/doc/registrant-api/v1/authentication.md b/doc/registrant-api/v1/authentication.md index 2b25a62c2..13638fca7 100644 --- a/doc/registrant-api/v1/authentication.md +++ b/doc/registrant-api/v1/authentication.md @@ -21,8 +21,6 @@ Values in brackets represent values that come from the id card certificate. | first_name | true | String | | Name of the customer (`GN`) | | last_name | true | String | | Name of the customer (`SN`) | | country | true | String | 'ee' | Code of the country that issued the id card (`C`) | -| issuing_authority | true | String | 'AS Sertifitseerimiskeskus' | | -| | | | | | #### Request @@ -36,7 +34,6 @@ Content-type: application/json "first_name": "Jan", "last_name": "Tamm", "country": "ee", - "issuing_authority": "AS Sertifitseerimiskeskus" } ``` From 1b9a504fb5c4ece69bd4a181d9c99229987d0ead Mon Sep 17 00:00:00 2001 From: Maciej Szlosarczyk Date: Wed, 18 Jul 2018 21:22:13 +0300 Subject: [PATCH 13/28] Remove country as identification parameter, restore server statuses --- doc/registrant-api/v1/authentication.md | 12 +++++------- doc/registrant-api/v1/domain_lock.md | 6 +++--- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/doc/registrant-api/v1/authentication.md b/doc/registrant-api/v1/authentication.md index 13638fca7..558bb1880 100644 --- a/doc/registrant-api/v1/authentication.md +++ b/doc/registrant-api/v1/authentication.md @@ -15,12 +15,11 @@ Returns a bearer token to be used for further API requests. Tokens are valid for 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`) | -| country | true | String | 'ee' | Code of the country that issued the id card (`C`) | +| 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 @@ -33,7 +32,6 @@ Content-type: application/json "ident": "30110100103", "first_name": "Jan", "last_name": "Tamm", - "country": "ee", } ``` diff --git a/doc/registrant-api/v1/domain_lock.md b/doc/registrant-api/v1/domain_lock.md index 6702d4233..0237a11cb 100644 --- a/doc/registrant-api/v1/domain_lock.md +++ b/doc/registrant-api/v1/domain_lock.md @@ -46,9 +46,9 @@ Content-Type: application/json "pending_json": {}, "force_delete_at": null, "statuses": [ - "clientUpdateProhibited", - "clientDeleteProhibited", - "clientTransferProhibited" + "serverUpdateProhibited", + "serverDeleteProhibited", + "serverTransferProhibited" ], "reserved": false, "status_notes": {}, From e75d962d39d6b5fc7a615168033405556d59b00d Mon Sep 17 00:00:00 2001 From: Maciej Szlosarczyk Date: Fri, 27 Jul 2018 11:19:18 +0300 Subject: [PATCH 14/28] Remove inconsistent usage of error/errors --- doc/registrant-api/v1/domain.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/registrant-api/v1/domain.md b/doc/registrant-api/v1/domain.md index 80290fbac..2e249423c 100644 --- a/doc/registrant-api/v1/domain.md +++ b/doc/registrant-api/v1/domain.md @@ -157,5 +157,5 @@ Content-Type: application/json HTTP/1.1 404 Content-Type: application/json -{ "error": "Domain not found" } +{ "errors": ["Domain not found"] } ``` From 3673c69319b87c82bc8b279583f3d463f347c50b Mon Sep 17 00:00:00 2001 From: Maciej Szlosarczyk Date: Wed, 1 Aug 2018 14:57:41 +0300 Subject: [PATCH 15/28] Add Registrant/Contacts endpoint --- .../api/v1/registrant/contacts_controller.rb | 47 +++++++++++ config/routes.rb | 1 + .../registrant_api_contacts_test.rb | 79 +++++++++++++++++++ 3 files changed, 127 insertions(+) create mode 100644 app/controllers/api/v1/registrant/contacts_controller.rb create mode 100644 test/integration/api/registrant/registrant_api_contacts_test.rb diff --git a/app/controllers/api/v1/registrant/contacts_controller.rb b/app/controllers/api/v1/registrant/contacts_controller.rb new file mode 100644 index 000000000..31d56885b --- /dev/null +++ b/app/controllers/api/v1/registrant/contacts_controller.rb @@ -0,0 +1,47 @@ +require 'rails5_api_controller_backport' +require 'auth_token/auth_token_decryptor' + +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: ['Contact not found'] }, status: :not_found + end + end + + private + + def set_contacts_pool + country_code, ident = current_user.registrant_ident.to_s.split '-' + @contacts_pool = Contact.where(country_code: country_code, ident: ident) + end + end + end + end +end diff --git a/config/routes.rb b/config/routes.rb index 3ae18a7cd..9229eb1b2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -24,6 +24,7 @@ Rails.application.routes.draw do post 'auth/eid', to: 'auth#eid' resources :domains, only: [:index] + resources :contacts, only: %i[index show] end end end diff --git a/test/integration/api/registrant/registrant_api_contacts_test.rb b/test/integration/api/registrant/registrant_api_contacts_test.rb new file mode 100644 index 000000000..a3367dd58 --- /dev/null +++ b/test/integration/api/registrant/registrant_api_contacts_test.rb @@ -0,0 +1,79 @@ +require 'test_helper' +require 'auth_token/auth_token_creator' + +class RegistrantApiContactsTest < ActionDispatch::IntegrationTest + def setup + super + + @user = users(:registrant) + @auth_headers = { 'HTTP_AUTHORIZATION' => auth_token } + 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(2, 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?('william-002')) + 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(2, response_json.count) + 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: ['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: ['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: ['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 From 079800172552ab94ff5bcc360288d76c5c2828ff Mon Sep 17 00:00:00 2001 From: Maciej Szlosarczyk Date: Tue, 7 Aug 2018 15:04:37 +0300 Subject: [PATCH 16/28] Make returned API errors more consistent --- app/controllers/api/v1/registrant/contacts_controller.rb | 2 +- .../api/registrant/registrant_api_contacts_test.rb | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/api/v1/registrant/contacts_controller.rb b/app/controllers/api/v1/registrant/contacts_controller.rb index 31d56885b..f252b3acd 100644 --- a/app/controllers/api/v1/registrant/contacts_controller.rb +++ b/app/controllers/api/v1/registrant/contacts_controller.rb @@ -31,7 +31,7 @@ module Api if @contact render json: @contact else - render json: { errors: ['Contact not found'] }, status: :not_found + render json: { errors: [{ base: ['Contact not found'] }] }, status: :not_found end end diff --git a/test/integration/api/registrant/registrant_api_contacts_test.rb b/test/integration/api/registrant/registrant_api_contacts_test.rb index a3367dd58..ff51494af 100644 --- a/test/integration/api/registrant/registrant_api_contacts_test.rb +++ b/test/integration/api/registrant/registrant_api_contacts_test.rb @@ -50,7 +50,7 @@ class RegistrantApiContactsTest < ActionDispatch::IntegrationTest assert_equal(401, response.status) json_body = JSON.parse(response.body, symbolize_names: true) - assert_equal({ errors: ['Not authorized'] }, json_body) + assert_equal({ errors: [base: ['Not authorized']] }, json_body) end def test_details_returns_401_without_authorization @@ -58,7 +58,7 @@ class RegistrantApiContactsTest < ActionDispatch::IntegrationTest assert_equal(401, response.status) json_body = JSON.parse(response.body, symbolize_names: true) - assert_equal({ errors: ['Not authorized'] }, json_body) + assert_equal({ errors: [base: ['Not authorized']] }, json_body) end def test_details_returns_404_for_non_existent_contact @@ -66,7 +66,7 @@ class RegistrantApiContactsTest < ActionDispatch::IntegrationTest assert_equal(404, response.status) json_body = JSON.parse(response.body, symbolize_names: true) - assert_equal({ errors: ['Contact not found'] }, json_body) + assert_equal({ errors: [base: ['Contact not found']] }, json_body) end private From 06dc954167b48f1854611282d02537cf6ced3a56 Mon Sep 17 00:00:00 2001 From: Maciej Szlosarczyk Date: Wed, 8 Aug 2018 09:15:18 +0300 Subject: [PATCH 17/28] Make tests inherit from ApplicationIntegrationTest class --- test/integration/api/registrant/registrant_api_contacts_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/api/registrant/registrant_api_contacts_test.rb b/test/integration/api/registrant/registrant_api_contacts_test.rb index ff51494af..c73c4a503 100644 --- a/test/integration/api/registrant/registrant_api_contacts_test.rb +++ b/test/integration/api/registrant/registrant_api_contacts_test.rb @@ -1,7 +1,7 @@ require 'test_helper' require 'auth_token/auth_token_creator' -class RegistrantApiContactsTest < ActionDispatch::IntegrationTest +class RegistrantApiContactsTest < ApplicationIntegrationTest def setup super From b18db190355fb38f5b7891ef32dd284a8025e39b Mon Sep 17 00:00:00 2001 From: Maciej Szlosarczyk Date: Thu, 9 Aug 2018 14:07:06 +0300 Subject: [PATCH 18/28] Fix error introduced by merge conflict --- config/routes.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/routes.rb b/config/routes.rb index 7a802a7a1..6af6623f8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -23,7 +23,7 @@ Rails.application.routes.draw do namespace :registrant do post 'auth/eid', to: 'auth#eid' - resources :domains, only: %i[index, show], param: :uuid + resources :domains, only: %i[index show], param: :uuid resources :contacts, only: %i[index show], param: :uuid end end From 4cc06692868e15a910b41beb17658d309396cda9 Mon Sep 17 00:00:00 2001 From: Maciej Szlosarczyk Date: Thu, 9 Aug 2018 15:04:22 +0300 Subject: [PATCH 19/28] Add business registry support for contacts controller --- .../api/v1/registrant/contacts_controller.rb | 18 +++++++--- .../api/v1/registrant/domains_controller.rb | 2 -- .../registrant/contacts_controller.rb | 2 +- .../registrant_api_contacts_test.rb | 33 +++++++++++++++++-- 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/app/controllers/api/v1/registrant/contacts_controller.rb b/app/controllers/api/v1/registrant/contacts_controller.rb index f252b3acd..2c8a1f70e 100644 --- a/app/controllers/api/v1/registrant/contacts_controller.rb +++ b/app/controllers/api/v1/registrant/contacts_controller.rb @@ -1,6 +1,3 @@ -require 'rails5_api_controller_backport' -require 'auth_token/auth_token_decryptor' - module Api module V1 module Registrant @@ -39,7 +36,20 @@ module Api def set_contacts_pool country_code, ident = current_user.registrant_ident.to_s.split '-' - @contacts_pool = Contact.where(country_code: country_code, ident: ident) + 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 diff --git a/app/controllers/api/v1/registrant/domains_controller.rb b/app/controllers/api/v1/registrant/domains_controller.rb index 27b7b6125..7209f8a10 100644 --- a/app/controllers/api/v1/registrant/domains_controller.rb +++ b/app/controllers/api/v1/registrant/domains_controller.rb @@ -1,5 +1,3 @@ -require 'rails5_api_controller_backport' - module Api module V1 module Registrant diff --git a/app/controllers/registrant/contacts_controller.rb b/app/controllers/registrant/contacts_controller.rb index f73650de2..267b4d68d 100644 --- a/app/controllers/registrant/contacts_controller.rb +++ b/app/controllers/registrant/contacts_controller.rb @@ -26,4 +26,4 @@ class Registrant::ContactsController < RegistrantController BusinessRegistryCache.fetch_by_ident_and_cc(ident, ident_cc).associated_domain_ids end end -end \ No newline at end of file +end diff --git a/test/integration/api/registrant/registrant_api_contacts_test.rb b/test/integration/api/registrant/registrant_api_contacts_test.rb index c73c4a503..28dd50d76 100644 --- a/test/integration/api/registrant/registrant_api_contacts_test.rb +++ b/test/integration/api/registrant/registrant_api_contacts_test.rb @@ -5,19 +5,30 @@ 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(2, json_body.count) + 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?('william-002')) + assert(array_of_contact_codes.include?('jane-001')) end def test_root_accepts_limit_and_offset_parameters @@ -28,7 +39,23 @@ class RegistrantApiContactsTest < ApplicationIntegrationTest get '/api/v1/registrant/contacts', {}, @auth_headers response_json = JSON.parse(response.body, symbolize_names: true) - assert_equal(2, response_json.count) + 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_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 From 256d2b7de424b914c7cd36628d73f58de3420086 Mon Sep 17 00:00:00 2001 From: Maciej Szlosarczyk Date: Thu, 9 Aug 2018 15:24:21 +0300 Subject: [PATCH 20/28] Add test for error in business registry --- .../api/v1/registrant/contacts_controller.rb | 2 +- .../api/registrant/registrant_api_contacts_test.rb | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/v1/registrant/contacts_controller.rb b/app/controllers/api/v1/registrant/contacts_controller.rb index 2c8a1f70e..5db175265 100644 --- a/app/controllers/api/v1/registrant/contacts_controller.rb +++ b/app/controllers/api/v1/registrant/contacts_controller.rb @@ -48,7 +48,7 @@ module Api @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"] }] }, + render json: { errors: [{ base: ["Business Registry not available"] }] }, status: :service_unavailable and return end end diff --git a/test/integration/api/registrant/registrant_api_contacts_test.rb b/test/integration/api/registrant/registrant_api_contacts_test.rb index 28dd50d76..ddeaee9f3 100644 --- a/test/integration/api/registrant/registrant_api_contacts_test.rb +++ b/test/integration/api/registrant/registrant_api_contacts_test.rb @@ -50,6 +50,17 @@ class RegistrantApiContactsTest < ApplicationIntegrationTest 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) From c186929be4704ba2f1ea26a3b4e86286dce1ac26 Mon Sep 17 00:00:00 2001 From: Maciej Szlosarczyk Date: Thu, 9 Aug 2018 15:28:43 +0300 Subject: [PATCH 21/28] Use single quote instead of double quotes --- app/controllers/api/v1/registrant/contacts_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/api/v1/registrant/contacts_controller.rb b/app/controllers/api/v1/registrant/contacts_controller.rb index 5db175265..de5ef9dcf 100644 --- a/app/controllers/api/v1/registrant/contacts_controller.rb +++ b/app/controllers/api/v1/registrant/contacts_controller.rb @@ -48,7 +48,7 @@ module Api @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"] }] }, + render json: { errors: [{ base: ['Business Registry not available'] }] }, status: :service_unavailable and return end end From 2f776f51ac6e09cf1b614137b42d2eca9dfb9743 Mon Sep 17 00:00:00 2001 From: Maciej Szlosarczyk Date: Tue, 14 Aug 2018 09:02:25 +0300 Subject: [PATCH 22/28] It seems like I made a mistake with the API URL prefix in docs This should be fixed now. I decided on fixing the documentation instead of the actual implementation, as it has no testing footprint --- doc/registrant-api/v1/authentication.md | 8 ++++---- doc/registrant-api/v1/contact.md | 12 ++++++------ doc/registrant-api/v1/domain.md | 12 ++++++------ doc/registrant-api/v1/domain_lock.md | 8 ++++---- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/doc/registrant-api/v1/authentication.md b/doc/registrant-api/v1/authentication.md index 558bb1880..20ad6edd6 100644 --- a/doc/registrant-api/v1/authentication.md +++ b/doc/registrant-api/v1/authentication.md @@ -7,7 +7,7 @@ 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 /repp/v1/registrant/auth/eid +## 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. @@ -24,7 +24,7 @@ Values in brackets represent values that come from the id card certificate. #### Request ``` -POST /repp/v1/auth/token HTTP/1.1 +POST /api/v1/auth/token HTTP/1.1 Accept: application/json Content-type: application/json @@ -48,7 +48,7 @@ Content-Type: application.json } ``` -## POST /repp/v1/auth/username -- NOT IMPLEMENTED +## POST /api/v1/auth/username -- NOT IMPLEMENTED #### Paramaters @@ -62,7 +62,7 @@ Values in brackets represent values that come from the id card certificate #### Request ``` -POST /repp/v1/auth/token HTTP/1.1 +POST /api/v1/auth/token HTTP/1.1 Accept: application/json Content-type: application/json ``` diff --git a/doc/registrant-api/v1/contact.md b/doc/registrant-api/v1/contact.md index ea28294d0..4e1c12bec 100644 --- a/doc/registrant-api/v1/contact.md +++ b/doc/registrant-api/v1/contact.md @@ -1,4 +1,4 @@ -## GET /repp/v1/registrant/contacts +## GET /api/v1/registrant/contacts Returns contacts of the current registrar. @@ -11,7 +11,7 @@ Returns contacts of the current registrar. #### Request ``` -GET /repp/v1/registrant/contacts?limit=1 HTTP/1.1 +GET /api/v1/registrant/contacts?limit=1 HTTP/1.1 Accept: application/json Authorization: Bearer Z2l0bGFiOmdoeXQ5ZTRmdQ== Content-Type: application/json @@ -59,13 +59,13 @@ Content-Type: application/json } ``` -## GET /repp/v1/registrant/contacts/$UUID +## GET /api/v1/registrant/contacts/$UUID Returns contacts of the current registrar. #### Request ``` -GET /repp/v1/registrant/contacts/84c62f3d-e56f-40fa-9ca4-dc0137778949 HTTP/1.1 +GET /api/v1/registrant/contacts/84c62f3d-e56f-40fa-9ca4-dc0137778949 HTTP/1.1 Accept: application/json Authorization: Bearer Z2l0bGFiOmdoeXQ5ZTRmdQ== Content-Type: application/json @@ -107,7 +107,7 @@ Content-Type: application/json } ``` -## PUT/PATCH /repp/v1/registrant/contacts/$UUID +## PATCH /api/v1/registrant/contacts/$UUID Update contact details for a contact. @@ -127,7 +127,7 @@ Update contact details for a contact. #### Request ``` -PUT /repp/v1/registrant/contacts/84c62f3d-e56f-40fa-9ca4-dc0137778949 HTTP/1.1 +PATCH /api/v1/registrant/contacts/84c62f3d-e56f-40fa-9ca4-dc0137778949 HTTP/1.1 Authorization: Bearer Z2l0bGFiOmdoeXQ5ZTRmdQ== Accept: application/json Content-type: application/json diff --git a/doc/registrant-api/v1/domain.md b/doc/registrant-api/v1/domain.md index 2e249423c..09495220d 100644 --- a/doc/registrant-api/v1/domain.md +++ b/doc/registrant-api/v1/domain.md @@ -1,6 +1,6 @@ # Domain related actions -## GET /repp/v1/registrant/domains +## GET /api/v1/registrant/domains Returns domains of the current registrant. @@ -15,7 +15,7 @@ Returns domains of the current registrant. #### Request ``` -GET repp/v1/registrant/domains?limit=1&details=true HTTP/1.1 +GET api/v1/registrant/domains?limit=1&details=true HTTP/1.1 Accept: application/json Authorization: Bearer Z2l0bGFiOmdoeXQ5ZTRmdQ== Content-Type: application/json @@ -70,14 +70,14 @@ Content-Type: application/json } ``` -## GET repp/v1/registrant/domains +## GET api/v1/registrant/domains Returns domain names with offset. #### Request ``` -GET repp/v1/registrant/domains?offset=1 HTTP/1.1 +GET api/v1/registrant/domains?offset=1 HTTP/1.1 Accept: application/json Authorization: Bearer Z2l0bGFiOmdoeXQ5ZTRmdQ== Content-Type: application/json @@ -96,14 +96,14 @@ Content-Type: application/json } ``` -## GET repp/v1/registrant/domains/$UUID +## GET api/v1/registrant/domains/$UUID Returns a single domain object. #### Request ``` -GET repp/v1/registrant/domains/98d1083a-8863-4153-93e4-caee4a013535 HTTP/1.1 +GET api/v1/registrant/domains/98d1083a-8863-4153-93e4-caee4a013535 HTTP/1.1 Accept: application/json Authorization: Bearer Z2l0bGFiOmdoeXQ5ZTRmdQ== Content-Type: application/json diff --git a/doc/registrant-api/v1/domain_lock.md b/doc/registrant-api/v1/domain_lock.md index 0237a11cb..5f275edb4 100644 --- a/doc/registrant-api/v1/domain_lock.md +++ b/doc/registrant-api/v1/domain_lock.md @@ -1,12 +1,12 @@ # Domain locks -## POST repp/v1/registrant/domains/$UUID/registry_lock +## POST api/v1/registrant/domains/$UUID/registry_lock Set a registry lock on a domain. #### Request ``` -POST repp/v1/registrant/domains/98d1083a-8863-4153-93e4-caee4a013535/registry_lock HTTP/1.1 +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 @@ -82,13 +82,13 @@ Content-Type: application/json ``` -## DELETE repp/v1/registrant/domains/$UUID/registry_lock +## DELETE api/v1/registrant/domains/$UUID/registry_lock Remove a registry lock. #### Request ``` -DELETE repp/v1/registrant/domains/98d1083a-8863-4153-93e4-caee4a013535/registry_lock HTTP/1.1 +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 From d4b0fecd4042c0e61aa43fdd6f921102a14e0ff9 Mon Sep 17 00:00:00 2001 From: Maciej Szlosarczyk Date: Tue, 14 Aug 2018 10:13:59 +0300 Subject: [PATCH 23/28] Get rid of CVE-2018-3769 by updating Grape --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index a8d6434e6..3285380f7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) From b4b404888bd23c2f4a20ef88cb968916b26963e0 Mon Sep 17 00:00:00 2001 From: Maciej Szlosarczyk Date: Wed, 15 Aug 2018 12:25:19 +0300 Subject: [PATCH 24/28] Disallow deposits that are lower than 0.01 EUR --- .../registrar/deposits_controller.rb | 6 +- app/models/deposit.rb | 16 +++-- spec/models/deposit_spec.rb | 39 ------------ test/models/deposit_test.rb | 59 +++++++++++++++++++ .../registrar_area/invoices/new_test.rb | 10 +--- 5 files changed, 76 insertions(+), 54 deletions(-) delete mode 100644 spec/models/deposit_spec.rb create mode 100644 test/models/deposit_test.rb diff --git a/app/controllers/registrar/deposits_controller.rb b/app/controllers/registrar/deposits_controller.rb index ec6d13977..818e38c6d 100644 --- a/app/controllers/registrar/deposits_controller.rb +++ b/app/controllers/registrar/deposits_controller.rb @@ -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 diff --git a/app/models/deposit.rb b/app/models/deposit.rb index 23045196a..589856437 100644 --- a/app/models/deposit.rb +++ b/app/models/deposit.rb @@ -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_deposit = [0.01, Setting.minimum_deposit].max + return if amount >= minimum_deposit + errors.add(:amount, I18n.t(:is_too_small_minimum_deposit_is, amount: minimum_deposit, + currency: 'EUR')) end def initialize(attributes = {}) @@ -24,10 +28,12 @@ class Deposit end def amount - BigDecimal.new(@amount.to_s.sub(/,/, '.')) + return BigDecimal.new('0.0') if @amount.blank? + BigDecimal.new(@amount, 10) end def issue_prepayment_invoice - valid? && registrar.issue_prepayment_invoice(amount, description) + return unless valid? + registrar.issue_prepayment_invoice(amount, description) end end diff --git a/spec/models/deposit_spec.rb b/spec/models/deposit_spec.rb deleted file mode 100644 index ff77dbd98..000000000 --- a/spec/models/deposit_spec.rb +++ /dev/null @@ -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 diff --git a/test/models/deposit_test.rb b/test/models/deposit_test.rb new file mode 100644 index 000000000..09f7de7f3 --- /dev/null +++ b/test/models/deposit_test.rb @@ -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,00" + assert_equal(BigDecimal.new("12.00"), @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 diff --git a/test/system/registrar_area/invoices/new_test.rb b/test/system/registrar_area/invoices/new_test.rb index b9b6b6db4..282b109dd 100644 --- a/test/system/registrar_area/invoices/new_test.rb +++ b/test/system/registrar_area/invoices/new_test.rb @@ -29,20 +29,16 @@ 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_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_difference 'Invoice.count', 1 do + assert_no_difference 'Invoice.count' do click_link_or_button 'Add' end - assert_text 'Please pay the following invoice' - assert_text 'Invoice no. 131050' - assert_text 'Subtotal 0,00 €' - assert_text 'Pay invoice' + assert_text 'Amount is too small. Minimum deposit is 0.01 EUR' end end From 0ea10452593cf5251b53fd0f95b476c23d896647 Mon Sep 17 00:00:00 2001 From: Maciej Szlosarczyk Date: Wed, 15 Aug 2018 12:46:36 +0300 Subject: [PATCH 25/28] Fix rubocop issues --- app/models/deposit.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/deposit.rb b/app/models/deposit.rb index 589856437..05197c5a1 100644 --- a/app/models/deposit.rb +++ b/app/models/deposit.rb @@ -14,7 +14,7 @@ class Deposit minimum_deposit = [0.01, Setting.minimum_deposit].max return if amount >= minimum_deposit errors.add(:amount, I18n.t(:is_too_small_minimum_deposit_is, amount: minimum_deposit, - currency: 'EUR')) + currency: 'EUR')) end def initialize(attributes = {}) @@ -28,8 +28,8 @@ class Deposit end def amount - return BigDecimal.new('0.0') if @amount.blank? - BigDecimal.new(@amount, 10) + return BigDecimal('0.0') if @amount.blank? + BigDecimal(@amount, 10) end def issue_prepayment_invoice From b8c082090e223e3a77f8c26f0dd56464db256fe2 Mon Sep 17 00:00:00 2001 From: Maciej Szlosarczyk Date: Wed, 15 Aug 2018 16:32:42 +0300 Subject: [PATCH 26/28] Rename a variable from minimum_deposit to minimum_allowed_amount --- app/models/deposit.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/deposit.rb b/app/models/deposit.rb index 05197c5a1..41d59ac4c 100644 --- a/app/models/deposit.rb +++ b/app/models/deposit.rb @@ -11,9 +11,9 @@ class Deposit validate :validate_amount def validate_amount - minimum_deposit = [0.01, Setting.minimum_deposit].max - return if amount >= minimum_deposit - errors.add(:amount, I18n.t(:is_too_small_minimum_deposit_is, amount: minimum_deposit, + 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 From c028c0e4771ac1f7815156a10b57d5790572693d Mon Sep 17 00:00:00 2001 From: Maciej Szlosarczyk Date: Wed, 15 Aug 2018 20:15:17 +0300 Subject: [PATCH 27/28] Add more tests to deposit handling --- app/models/deposit.rb | 2 +- test/models/deposit_test.rb | 4 +-- .../registrar_area/invoices/new_test.rb | 29 +++++++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/app/models/deposit.rb b/app/models/deposit.rb index 41d59ac4c..a3b898047 100644 --- a/app/models/deposit.rb +++ b/app/models/deposit.rb @@ -29,7 +29,7 @@ class Deposit def amount return BigDecimal('0.0') if @amount.blank? - BigDecimal(@amount, 10) + BigDecimal(@amount.to_s.gsub(/,/, '.'), 10) end def issue_prepayment_invoice diff --git a/test/models/deposit_test.rb b/test/models/deposit_test.rb index 09f7de7f3..b7510b960 100644 --- a/test/models/deposit_test.rb +++ b/test/models/deposit_test.rb @@ -40,8 +40,8 @@ class DepositTest < ActiveSupport::TestCase @deposit.amount = "12.00" assert_equal(BigDecimal.new("12.00"), @deposit.amount) - @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 diff --git a/test/system/registrar_area/invoices/new_test.rb b/test/system/registrar_area/invoices/new_test.rb index 282b109dd..a5a72fbe8 100644 --- a/test/system/registrar_area/invoices/new_test.rb +++ b/test/system/registrar_area/invoices/new_test.rb @@ -29,6 +29,22 @@ class NewInvoiceTest < ApplicationSystemTestCase assert_text 'Pay invoice' end + def test_create_new_invoice_with_comma_in_number + visit registrar_invoices_path + click_link_or_button 'Add deposit' + fill_in 'Amount', with: '200,00' + fill_in 'Description', with: 'My first invoice' + + assert_difference 'Invoice.count', 1 do + click_link_or_button 'Add' + end + + assert_text 'Please pay the following invoice' + assert_text 'Invoice no. 131050' + 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' @@ -41,4 +57,17 @@ class NewInvoiceTest < ApplicationSystemTestCase 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 From 066123bcf731fb568c1dda01c41344e1ec572bbc Mon Sep 17 00:00:00 2001 From: Maciej Szlosarczyk Date: Thu, 16 Aug 2018 13:33:09 +0300 Subject: [PATCH 28/28] Replace gsub with tr for better performance --- app/models/deposit.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/deposit.rb b/app/models/deposit.rb index a3b898047..2eff26bcc 100644 --- a/app/models/deposit.rb +++ b/app/models/deposit.rb @@ -29,7 +29,7 @@ class Deposit def amount return BigDecimal('0.0') if @amount.blank? - BigDecimal(@amount.to_s.gsub(/,/, '.'), 10) + BigDecimal(@amount.to_s.tr(',', '.'), 10) end def issue_prepayment_invoice