From 3dc124435cdd988ff83a1fb9d3316cf55c6853bf Mon Sep 17 00:00:00 2001 From: Kerem Bozdas Date: Thu, 6 Jul 2023 21:15:43 +0300 Subject: [PATCH] Add support for querying government registry --- README.md | 27 ++- bin/turkish_id | 38 +++- lib/turkish_id.rb | 19 ++ ...merically_valid_unregistered_id_number.yml | 168 ++++++++++++++++++ ...overnment_registry_for_valid_id_number.yml | 168 ++++++++++++++++++ spec/spec_helper.rb | 11 ++ spec/turkish_id_spec.rb | 54 +++++- turkish_id.gemspec | 6 +- 8 files changed, 478 insertions(+), 13 deletions(-) create mode 100644 spec/fixtures/vcr_cassettes/TurkishId/checks_government_registry_for_numerically_valid_unregistered_id_number.yml create mode 100644 spec/fixtures/vcr_cassettes/TurkishId/checks_government_registry_for_valid_id_number.yml diff --git a/README.md b/README.md index 13e9513..4924ef7 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Status](https://img.shields.io/github/actions/workflow/status/krmbzds/turkish_id/test.yml?branch=master)](https://github.com/krmbzds/turkish_id/actions/workflows/test.yml) [![Coverage](https://img.shields.io/coveralls/github/krmbzds/turkish_id)](https://coveralls.io/github/krmbzds/turkish_id) [![Downloads](https://img.shields.io/gem/dt/turkish_id.svg)](https://rubygems.org/gems/turkish_id) -[![Dependencies](https://img.shields.io/badge/dependencies-none-brightgreen.svg)](https://rubygems.org/gems/turkish_id) +[![Dependencies](https://img.shields.io/badge/dependencies-1-yellowgreen.svg)](https://rubygems.org/gems/turkish_id) [![Maintainability](https://img.shields.io/codeclimate/maintainability/krmbzds/turkish_id)](https://codeclimate.com/github/krmbzds/turkish_id) [![Version](https://img.shields.io/gem/v/turkish_id.svg)](https://github.com/krmbzds/turkish_id) [![Docs](https://img.shields.io/badge/rubydoc-info-blue.svg)](https://www.rubydoc.info/gems/turkish_id) @@ -51,6 +51,29 @@ $ turkish_id 10000000079 #=> false $ echo $? #=> 1 ``` +### Querying the Government Registry + +Create a new instance: + +```rb +identity_number = TurkishId.new(10000000146) +``` + +Use ```registered?``` method to query the government registry: + +```rb +identity_number.registered?("Ahmet", "Yılmaz", 1987) #=> false +``` + +Or use the command line executable: + +```sh +$ turkish_id 10000000078 Ahmet Yılmaz 2000 #=> true +$ echo $? #=> 0 +$ turkish_id 19930509666 Cypher Punk 1984 #=> false +$ echo $? #=> 1 +``` + ### Generating Relatives You can generate ID numbers for your younger or elder relatives. @@ -99,7 +122,7 @@ There are three conditions for a valid identification number: Where ```dn``` refers to the ```n-th``` digit of the identification number. -Remember that a valid identification number does not imply the existence of an ID. It could only be used as a preliminary check e.g. before querying a government website. This is very similar to credit card validation. +Remember that a valid identification number does not imply the existence of an ID. It could only be used as a preliminary check e.g. before [querying a government website](#querying-the-government-registry). This is very similar to credit card validation. ## Support diff --git a/bin/turkish_id b/bin/turkish_id index 016db5f..8b3784d 100755 --- a/bin/turkish_id +++ b/bin/turkish_id @@ -5,21 +5,41 @@ lib = File.expand_path(File.dirname(__FILE__) + "/../lib") $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib) require "turkish_id" +require "turkish_id/version" -@help = ' +HELP = ' Usage - turkish_id QUERY + turkish_id ID_NUMBER [GIVEN_NAME SURNAME YEAR_OF_BIRTH] + + Description + turkish_id validates Turkish identity numbers. + Only providing ID_NUMBER performs numerical validation (offline). + Providing all arguments will query government registry (online). Examples turkish_id 10000000078 - turkish_id 10000000146 + turkish_id 10000000146 Ahmet Yılmaz 1984 + turkish_id 10005999902 "Ayşe Nur" Yılmaz 1996 ' -if ARGV[0] - result = TurkishId.new(ARGV[0]).valid? - $stdout.puts result - exit result ? 0 : 1 -else - $stdout.puts @help +result = + case ARGV.length + when 1 then TurkishId.new(ARGV[0]).valid? + when 4 then TurkishId.new(ARGV[0]).registered?(ARGV[1], ARGV[2], ARGV[3]) + else $stdout.puts HELP + exit 1 + end + +if ["-?", "-h", "--help", "--usage"].include?(ARGV[0]) + $stdout.puts HELP + exit 1 end + +if ["-v", "-V", "--version"].include?(ARGV[0]) + $stdout.puts TurkishId::VERSION + exit 0 +end + +$stdout.puts result +exit result ? 0 : 1 diff --git a/lib/turkish_id.rb b/lib/turkish_id.rb index 0d73f96..e968080 100644 --- a/lib/turkish_id.rb +++ b/lib/turkish_id.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "turkish_id/version" +require "savon" class TurkishId attr_reader :id_number, :checksum, :elder_relative, :younger_relative @@ -19,6 +20,10 @@ def valid? true end + def registered?(given_name, surname, year_of_birth) + valid? && query_government_registry(given_name, surname, year_of_birth) + end + private def calculate_checksum(id_array) @@ -76,4 +81,18 @@ def join_num(id_array) def get_core(id_array) join_num(split_num(id_array).take(9)) end + + def query_government_registry(given_name, surname, year_of_birth) + client = Savon.client(wsdl: "https://tckimlik.nvi.gov.tr/Service/KPSPublic.asmx?WSDL") + response = client.call(:tc_kimlik_no_dogrula, message: { + "TCKimlikNo" => join_num(@id_number), + "Ad" => given_name.upcase(:turkic), + "Soyad" => surname.upcase(:turkic), + "DogumYili" => String(Date.new(Integer(year_of_birth)).year) + }) + + response.body.dig(:tc_kimlik_no_dogrula_response, :tc_kimlik_no_dogrula_result).is_a?(TrueClass) + rescue TypeError, ArgumentError, NoMethodError + false + end end diff --git a/spec/fixtures/vcr_cassettes/TurkishId/checks_government_registry_for_numerically_valid_unregistered_id_number.yml b/spec/fixtures/vcr_cassettes/TurkishId/checks_government_registry_for_numerically_valid_unregistered_id_number.yml new file mode 100644 index 0000000..aeada65 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/TurkishId/checks_government_registry_for_numerically_valid_unregistered_id_number.yml @@ -0,0 +1,168 @@ +--- +http_interactions: +- request: + method: get + uri: https://tckimlik.nvi.gov.tr/Service/KPSPublic.asmx?WSDL + body: + encoding: US-ASCII + string: '' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Cache-Control: + - private, max-age=0 + Content-Type: + - text/xml; charset=utf-8 + Vary: + - Accept-Encoding + Access-Control-Allow-Headers: + - Origin, X-Requested-With, Content-Type, Accept + Access-Control-Allow-Methods: + - POST,GET + Strict-Transport-Security: + - max-age=16070400;includeSubDomains + X-Frame-Options: + - SAMEORIGIN + X-Xss-Protection: + - 1;mode=block + X-Content-Type-Options: + - nosniff + Content-Security-Policy: + - default-src 'self' 'unsafe-inline'; font-src 'self' data:; script-src 'self' + 'unsafe-eval' 'unsafe-inline' https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/; + img-src 'self' 'unsafe-inline' data:;frame-src https://www.google.com/recaptcha/ + https://tckimliktest.nvi.gov.tr https://tckimlik.nvi.gov.tr; style-src 'self' + 'unsafe-inline' + Referer-Policy: + - no-referer + Date: + - Thu, 06 Jul 2023 15:17:32 GMT + Content-Length: + - '3418' + Set-Cookie: + - TS01326bb0=0179b2ce4547096aa436bb2f57d849173f8b4ffd6d03b39f2ac8f35c98b9fb0e25ff63b08dec6aa71f4c4f6f130f90bf37e419b765; + Path=/; Domain=.tckimlik.nvi.gov.tr + body: + encoding: ASCII-8BIT + string: "\r\n\r\n \r\n \r\n + \ \r\n \r\n + \ \r\n \r\n \r\n \r\n \r\n \r\n + \ \r\n \r\n \r\n + \ \r\n \r\n \r\n \r\n \r\n \r\n + \ \r\n \r\n \r\n + \ \r\n + \ \r\n \r\n + \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n + \ \r\n \r\n + \ \r\n + \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n + \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n + \ \r\n \r\n \r\n \r\n \r\n + \ \r\n \r\n \r\n" + recorded_at: Thu, 06 Jul 2023 15:17:32 GMT +- request: + method: post + uri: https://tckimlik.nvi.gov.tr/Service/KPSPublic.asmx + body: + encoding: UTF-8 + string: 99997183780RAVEBASEPHASE + 91997 + headers: + Soapaction: + - '"http://tckimlik.nvi.gov.tr/WS/TCKimlikNoDogrula"' + Content-Type: + - text/xml;charset=UTF-8 + Content-Length: + - '875' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Cache-Control: + - private, max-age=0 + Content-Type: + - text/xml; charset=utf-8 + Vary: + - Accept-Encoding + Access-Control-Allow-Headers: + - Origin, X-Requested-With, Content-Type, Accept + Access-Control-Allow-Methods: + - POST,GET + Strict-Transport-Security: + - max-age=16070400;includeSubDomains + X-Frame-Options: + - SAMEORIGIN + X-Xss-Protection: + - 1;mode=block + X-Content-Type-Options: + - nosniff + Content-Security-Policy: + - default-src 'self' 'unsafe-inline'; font-src 'self' data:; script-src 'self' + 'unsafe-eval' 'unsafe-inline' https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/; + img-src 'self' 'unsafe-inline' data:;frame-src https://www.google.com/recaptcha/ + https://tckimliktest.nvi.gov.tr https://tckimlik.nvi.gov.tr; style-src 'self' + 'unsafe-inline' + Referer-Policy: + - no-referer + Date: + - Thu, 06 Jul 2023 15:17:32 GMT + Content-Length: + - '395' + Set-Cookie: + - TS01326bb0=0179b2ce450bc4e83b823117688a50a716626538b3aab2380963bf3ee02c347c1cfb765119d4bd2948299fafdcfa605f8ff0137b15; + Path=/; Domain=.tckimlik.nvi.gov.tr + body: + encoding: ASCII-8BIT + string: false + recorded_at: Thu, 06 Jul 2023 15:17:32 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/fixtures/vcr_cassettes/TurkishId/checks_government_registry_for_valid_id_number.yml b/spec/fixtures/vcr_cassettes/TurkishId/checks_government_registry_for_valid_id_number.yml new file mode 100644 index 0000000..cbbc764 --- /dev/null +++ b/spec/fixtures/vcr_cassettes/TurkishId/checks_government_registry_for_valid_id_number.yml @@ -0,0 +1,168 @@ +--- +http_interactions: +- request: + method: get + uri: https://tckimlik.nvi.gov.tr/Service/KPSPublic.asmx?WSDL + body: + encoding: US-ASCII + string: '' + headers: + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Cache-Control: + - private, max-age=0 + Content-Type: + - text/xml; charset=utf-8 + Vary: + - Accept-Encoding + Access-Control-Allow-Headers: + - Origin, X-Requested-With, Content-Type, Accept + Access-Control-Allow-Methods: + - POST,GET + Strict-Transport-Security: + - max-age=16070400;includeSubDomains + X-Frame-Options: + - SAMEORIGIN + X-Xss-Protection: + - 1;mode=block + X-Content-Type-Options: + - nosniff + Content-Security-Policy: + - default-src 'self' 'unsafe-inline'; font-src 'self' data:; script-src 'self' + 'unsafe-eval' 'unsafe-inline' https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/; + img-src 'self' 'unsafe-inline' data:;frame-src https://www.google.com/recaptcha/ + https://tckimliktest.nvi.gov.tr https://tckimlik.nvi.gov.tr; style-src 'self' + 'unsafe-inline' + Referer-Policy: + - no-referer + Date: + - Thu, 06 Jul 2023 15:17:32 GMT + Content-Length: + - '3418' + Set-Cookie: + - TS01326bb0=0179b2ce455a621eed93512b85f6dcaf64f497a37dc2958f6cc5e9352b533efd0881d0160baf04fcb5ab344e4f4a3334f062aea547; + Path=/; Domain=.tckimlik.nvi.gov.tr + body: + encoding: ASCII-8BIT + string: "\r\n\r\n \r\n \r\n + \ \r\n \r\n + \ \r\n \r\n \r\n \r\n \r\n \r\n + \ \r\n \r\n \r\n + \ \r\n \r\n \r\n \r\n \r\n \r\n + \ \r\n \r\n \r\n + \ \r\n + \ \r\n \r\n + \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n + \ \r\n \r\n + \ \r\n + \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n + \ \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n + \ \r\n \r\n \r\n \r\n \r\n + \ \r\n \r\n \r\n" + recorded_at: Thu, 06 Jul 2023 15:17:32 GMT +- request: + method: post + uri: https://tckimlik.nvi.gov.tr/Service/KPSPublic.asmx + body: + encoding: UTF-8 + string: 10000000146GAZİ + MUSTAFA KEMAL PAŞAATATÜRK1881 + headers: + Soapaction: + - '"http://tckimlik.nvi.gov.tr/WS/TCKimlikNoDogrula"' + Content-Type: + - text/xml;charset=UTF-8 + Content-Length: + - '893' + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + Accept: + - "*/*" + User-Agent: + - Ruby + response: + status: + code: 200 + message: OK + headers: + Cache-Control: + - private, max-age=0 + Content-Type: + - text/xml; charset=utf-8 + Vary: + - Accept-Encoding + Access-Control-Allow-Headers: + - Origin, X-Requested-With, Content-Type, Accept + Access-Control-Allow-Methods: + - POST,GET + Strict-Transport-Security: + - max-age=16070400;includeSubDomains + X-Frame-Options: + - SAMEORIGIN + X-Xss-Protection: + - 1;mode=block + X-Content-Type-Options: + - nosniff + Content-Security-Policy: + - default-src 'self' 'unsafe-inline'; font-src 'self' data:; script-src 'self' + 'unsafe-eval' 'unsafe-inline' https://www.google.com/recaptcha/ https://www.gstatic.com/recaptcha/; + img-src 'self' 'unsafe-inline' data:;frame-src https://www.google.com/recaptcha/ + https://tckimliktest.nvi.gov.tr https://tckimlik.nvi.gov.tr; style-src 'self' + 'unsafe-inline' + Referer-Policy: + - no-referer + Date: + - Thu, 06 Jul 2023 15:17:32 GMT + Content-Length: + - '395' + Set-Cookie: + - TS01326bb0=0179b2ce4563cbc519f948262ee73006dff9420994d611107c632e1a7b054f00b3b5729529e2fcf94a8cf7a2ca3574446a9c16f8d5; + Path=/; Domain=.tckimlik.nvi.gov.tr + body: + encoding: ASCII-8BIT + string: true + recorded_at: Thu, 06 Jul 2023 15:17:32 GMT +recorded_with: VCR 6.2.0 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 35a9931..7cb30aa 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -3,5 +3,16 @@ require "coveralls" Coveralls.wear! +require "webmock/rspec" +require "vcr" + $LOAD_PATH.unshift File.expand_path("../lib", __dir__) require "turkish_id" + +VCR.configure do |c| + c.hook_into :webmock + c.configure_rspec_metadata! + c.ignore_localhost = true + c.cassette_library_dir = "spec/fixtures/vcr_cassettes" + c.allow_http_connections_when_no_cassette = true +end diff --git a/spec/turkish_id_spec.rb b/spec/turkish_id_spec.rb index 1a43ed8..a1ead22 100644 --- a/spec/turkish_id_spec.rb +++ b/spec/turkish_id_spec.rb @@ -2,7 +2,7 @@ require "spec_helper" -describe TurkishId do +describe TurkishId, vcr: true do it "has a version number" do expect(TurkishId::VERSION).not_to be nil end @@ -83,4 +83,56 @@ elder_relatives = identity_number.elder_relative.take(3) expect(elder_relatives).to eq([]) end + + it "checks government registry for valid id number" do + identity_number = TurkishId.new(10_000_000_146) + expect(identity_number.valid?).to eq(true) + expect(identity_number.registered?("Gazi Mustafa Kemal Paşa", "Atatürk", 1881)).to eq(true) + end + + it "checks government registry for numerically valid unregistered id number" do + identity_number = TurkishId.new(99_997_183_780) + expect(identity_number.valid?).to eq(true) + expect(identity_number.registered?("RaveBase", "Phase 9", 1997)).to eq(false) + end + + it "does not query government registry for invalid id number" do + stub_any = stub_request(:any, "tckimlik.nvi.gov.tr") + identity_number = TurkishId.new(10_000_000_000) + expect(identity_number.valid?).to eq(false) + expect(identity_number.registered?("Nothing", "Happened", 1989)).to eq(false) + assert_not_requested(stub_any) + end + + it "does not query government registry for invalid year of birth" do + stub_any = stub_request(:any, "tckimlik.nvi.gov.tr") + identity_number = TurkishId.new(10_000_000_146) + expect(identity_number.valid?).to eq(true) + expect(identity_number.registered?("Ahmet", "Yılmaz", "2nd Millennium")).to eq(false) + assert_not_requested(stub_any) + end + + it "does not query government registry for nil year of birth" do + stub_any = stub_request(:any, "tckimlik.nvi.gov.tr") + identity_number = TurkishId.new(10_000_000_146) + expect(identity_number.valid?).to eq(true) + expect(identity_number.registered?("Ayşe", "Yılmaz", nil)).to eq(false) + assert_not_requested(stub_any) + end + + it "does not query government registry for nil given name" do + stub_any = stub_request(:any, "tckimlik.nvi.gov.tr") + identity_number = TurkishId.new(10_000_000_146) + expect(identity_number.valid?).to eq(true) + expect(identity_number.registered?(nil, "Yılmaz", 2001)).to eq(false) + assert_not_requested(stub_any) + end + + it "does not query government registry for nil surname" do + stub_any = stub_request(:any, "tckimlik.nvi.gov.tr") + identity_number = TurkishId.new(10_000_000_146) + expect(identity_number.valid?).to eq(true) + expect(identity_number.registered?("Ayşe", nil, 2000)).to eq(false) + assert_not_requested(stub_any) + end end diff --git a/turkish_id.gemspec b/turkish_id.gemspec index 23bb21e..365cd39 100644 --- a/turkish_id.gemspec +++ b/turkish_id.gemspec @@ -21,8 +21,12 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib", "lib/turkish_id"] spec.add_development_dependency "bundler" + spec.add_development_dependency "coveralls_reborn" spec.add_development_dependency "rake" spec.add_development_dependency "rspec" spec.add_development_dependency "standard" - spec.add_development_dependency "coveralls_reborn" + spec.add_development_dependency "vcr" + spec.add_development_dependency "webmock" + + spec.add_runtime_dependency "savon" end