identification 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.travis.yml +4 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +109 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/identification.gemspec +25 -0
- data/lib/identification.rb +6 -0
- data/lib/identification/document.rb +81 -0
- data/lib/identification/dvla.rb +49 -0
- data/lib/identification/parser/dvla.rb +75 -0
- data/lib/identification/parser/parser.rb +36 -0
- data/lib/identification/parser/passport.rb +87 -0
- data/lib/identification/passport.rb +65 -0
- data/lib/identification/version.rb +4 -0
- metadata +105 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c49ff5d80e197aa928760aa1193cb15fbe9b5917
|
4
|
+
data.tar.gz: 5335b98ae3ad61dd9e27a3ec98ebf807191a5bab
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c7e1f37bd66f8811ef89fce412c3e5ff9f265760ff8000610e1d3c857bca9f7a2d1ee74bb17f22bd35e7bf22b9cb8a6fe4fed90c2719307eca131d1911c35d4e
|
7
|
+
data.tar.gz: e537e3b1a55f025e65f02d7f2f6dca7a5dd741303daeb922d6d57acb021a8f2671962e57075f3886029d95edcb6d76788be7f78614d00c7ae52c785fa3486b0e
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Ruby Nealon
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
# Identification
|
2
|
+
|
3
|
+
A suite of tools for working with various identity documents. Allows verification of Passports and (coming very soon) UK Drivers Licenses. Generation tools coming soon.
|
4
|
+
|
5
|
+
## Examples
|
6
|
+
Create a Passport instance with an MRZ Code.
|
7
|
+
```ruby
|
8
|
+
passport = Identification::Passport.new mrz_line_1: "mrz_line_1", mrz_line_2: "mrz_line_2"
|
9
|
+
```
|
10
|
+
|
11
|
+
and you're done! Use it like this:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
passport.valid?
|
15
|
+
# => true
|
16
|
+
passport.last_name
|
17
|
+
# => LAST
|
18
|
+
passport.gender
|
19
|
+
# => M
|
20
|
+
```
|
21
|
+
|
22
|
+
## Installation
|
23
|
+
|
24
|
+
Add this line to your application's Gemfile:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
gem 'identification'
|
28
|
+
```
|
29
|
+
|
30
|
+
And then execute:
|
31
|
+
|
32
|
+
$ bundle
|
33
|
+
|
34
|
+
Or install it yourself as:
|
35
|
+
|
36
|
+
$ gem install identification
|
37
|
+
|
38
|
+
## Usage
|
39
|
+
|
40
|
+
### Passport
|
41
|
+
|
42
|
+
Setup your passport instance like so:
|
43
|
+
```ruby
|
44
|
+
passport = Identification::Passport.new mrz_line_1: "mrz_line_1", mrz_line_1: "mrz_line_2"
|
45
|
+
```
|
46
|
+
|
47
|
+
```
|
48
|
+
mrz_line_1: The first line of a Passport MRZ (the long strip of text on the bottom). (string)
|
49
|
+
mrz_line_2: The second line of a Passport MRZ (the long strip of text on the bottom). (string)
|
50
|
+
```
|
51
|
+
|
52
|
+
It then can be called using any of the folllowing methods/accessors:
|
53
|
+
|
54
|
+
```
|
55
|
+
valid? : Returns true if it's a valid passport MRZ, false if it's not.
|
56
|
+
issuing_state : Issuing country of the passport
|
57
|
+
last_name : Last name on the passport
|
58
|
+
first_names : Array of first names on the passport (First and any middle names)
|
59
|
+
passport_number : Passport Number
|
60
|
+
nationality : Nationality of the person on the passport. Different from the issuing state (eg. Swedish person who is also a citizen of UK.)
|
61
|
+
date_of_birth : Date of Birth, given as a Ruby Date object.
|
62
|
+
gender : Gender, given as M, F or nil if not specified.
|
63
|
+
expiry_date : Expiry date of the passport, given as a Ruby Date object.
|
64
|
+
personal_number : Personal number, if chosen to be given by issuer. Nil if not specified.
|
65
|
+
```
|
66
|
+
|
67
|
+
### UK Drivers License
|
68
|
+
|
69
|
+
**Note:** *This does not verify check digits yet. I am currently in a 'legal argument' with the DVLA over whether it is in the public interest to release the check digit algorith as they're claiming it is a security threat... Feel free to follow the [FOI request on WhatDoTheyKnow](https://www.whatdotheyknow.com/request/what_check_digit_algorithm_is_us#incoming-670731) and I'll update the gem once I have a response.*
|
70
|
+
|
71
|
+
Setup your UK Drivers License (DVLA issued only) instance like so:
|
72
|
+
```ruby
|
73
|
+
ukdl = Identification::DVLA.new driver_number: "your_driver_number"
|
74
|
+
```
|
75
|
+
|
76
|
+
```
|
77
|
+
driver_number: driver number from Section 5 of a license, 16 or 18 digit (if 16 digit license_numbers will be set to 00) (string)
|
78
|
+
```
|
79
|
+
|
80
|
+
It then can be called using any of the folllowing methods/accessors:
|
81
|
+
|
82
|
+
```
|
83
|
+
valid? : Returns true if it's a valid UK drivers license number, false if it's not.
|
84
|
+
last_name : First 5 letters of the last name of the license holder
|
85
|
+
first_initials : First initial(s) of the license holder
|
86
|
+
date_of_birth : Date of Birth, given as a Ruby Date object.
|
87
|
+
gender : Gender, given as M, F or nil if not specified.
|
88
|
+
license_numbers : License number (2 digit number at end of section 5)
|
89
|
+
```
|
90
|
+
|
91
|
+
## Development
|
92
|
+
|
93
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
94
|
+
|
95
|
+
To install the gem onto your local machine, run `bundle exec rake install`.
|
96
|
+
|
97
|
+
## Contributing
|
98
|
+
|
99
|
+
Bug reports and pull requests are more than welcome on GitHub at https://github.com/rubymeow/identification! <3
|
100
|
+
|
101
|
+
|
102
|
+
## License
|
103
|
+
|
104
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
105
|
+
|
106
|
+
MRZ parsing based on Document 9303 (Machine Readable Travel Documents - Part 1 - Machine Readable Passports. Volume 1 - Passports with Machine Readable Data Stored in Optical Character Recognition format) by the ICAO which can be found here:
|
107
|
+
http://www.icao.int/publications/pages/publication.aspx?docnum=9303
|
108
|
+
|
109
|
+
Warning: This is by no means a KYC solution on it's own. This merely parses and validates numbers/codes with their given algorithms to make sure they're not an edited forgery. It's not difficult to circumvent this, and this should only be used to validate input or expedite information checking.
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'identification'
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require 'irb'
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'identification/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'identification'
|
8
|
+
spec.version = Identification::VERSION
|
9
|
+
spec.authors = ['Ruby']
|
10
|
+
spec.email = ['hi@ruby.sh']
|
11
|
+
|
12
|
+
spec.summary = 'A suite of tools for working with various identity documents.'
|
13
|
+
spec.description = 'A suite of tools for verifying and generating identity documents. Support is currently provided for parsing passports, and UK Drivers Liceneses are in development. Generation will be added in the near future.'
|
14
|
+
spec.homepage = 'https://github.com/rubymeow/identification'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = 'exe'
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ['lib']
|
21
|
+
|
22
|
+
spec.add_development_dependency 'bundler', '~> 1.10'
|
23
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
24
|
+
spec.add_development_dependency 'minitest'
|
25
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Identification
|
2
|
+
# Represents an identity document.
|
3
|
+
# This class carries base methods that will appear in all document types.
|
4
|
+
#
|
5
|
+
# @abstract Subclass and implement a {#parse} method (setting the fields and @validity)
|
6
|
+
# and alter {#initialize} to implement a custom Document class. See other classes for examples.
|
7
|
+
class Document
|
8
|
+
attr_accessor :last_name, :date_of_birth, :gender
|
9
|
+
|
10
|
+
# Creates an instance of a documents.
|
11
|
+
# Will automatically parse if the drivers number is given, and will automatically generate if all necessary fields are set.
|
12
|
+
#
|
13
|
+
# @param [Hash] details of the paramaters
|
14
|
+
# @option opts [String] :last_name surname of the individual (only set for generating)
|
15
|
+
# @option opts [String] :date_of_birth date of birth of the individual (only set for generating)
|
16
|
+
# @option opts [String] :gender gender of the individual (only set for generating)
|
17
|
+
def initialize(params = {})
|
18
|
+
@last_name = params[:last_name]
|
19
|
+
@date_of_birth = params[:date_of_birth]
|
20
|
+
@gender = params[:gender]
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns true if the drivers license is valid.
|
24
|
+
# Requires the driver number to be parsed before it can be called.
|
25
|
+
#
|
26
|
+
# @return [Boolean] whether or not the passport is valid
|
27
|
+
# @raise [RuntimeError] if no mrz has been parsed yet.
|
28
|
+
def valid?
|
29
|
+
if defined? @validity
|
30
|
+
return @validity
|
31
|
+
else
|
32
|
+
fail 'No document number has been parsed.'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns the age of the individual.
|
37
|
+
# Requires a date of birth to be set before it can be called.
|
38
|
+
#
|
39
|
+
# @return [Boolean] age of the individual
|
40
|
+
# @raise [RuntimeError] if no date of birth is set
|
41
|
+
def age
|
42
|
+
if !@date_of_birth.nil?
|
43
|
+
now = Time.now.utc.to_date
|
44
|
+
dob = @date_of_birth
|
45
|
+
return now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
|
46
|
+
else
|
47
|
+
fail 'No date of birth has been set.'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Returns true if the individual is over 18 (18+)
|
52
|
+
# Requires a date of birth to be set before it can be called.
|
53
|
+
#
|
54
|
+
# @return [Boolean] whether or not the individual is over 18
|
55
|
+
# @raise [RuntimeError] if no date of birth is set
|
56
|
+
def over_18?
|
57
|
+
if !@date_of_birth.nil?
|
58
|
+
return true if age >= 18
|
59
|
+
else
|
60
|
+
fail 'No date of birth has been set.'
|
61
|
+
end
|
62
|
+
|
63
|
+
false
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns true if the individual is over 21 (21+)
|
67
|
+
# Requires a date of birth to be set before it can be called.
|
68
|
+
#
|
69
|
+
# @return [Boolean] whether or not the individual is over 21
|
70
|
+
# @raise [RuntimeError] if no date of birth is set
|
71
|
+
def over_21?
|
72
|
+
if !@date_of_birth.nil?
|
73
|
+
return true if age >= 21
|
74
|
+
else
|
75
|
+
fail 'No date of birth has been set.'
|
76
|
+
end
|
77
|
+
|
78
|
+
false
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'identification/document'
|
2
|
+
require 'identification/parser/dvla'
|
3
|
+
|
4
|
+
module Identification
|
5
|
+
# Represents a UK DVLA-issued Drivers License.
|
6
|
+
class DVLA < Document
|
7
|
+
attr_accessor :driver_number, :first_initials, :license_numbers
|
8
|
+
|
9
|
+
# Creates an instance of a drivers license.
|
10
|
+
# Will automatically parse if the drivers number is given, and will automatically generate if all necessary fields are set.
|
11
|
+
#
|
12
|
+
# @param [Hash] details of the paramaters
|
13
|
+
# @option opts [String] :driver_number 18 digit driver number, section 5 on a drivers license of the individual (only set for parsing)
|
14
|
+
# @option opts [String] :first_initials first initials of the individual (only set for generating)
|
15
|
+
# @option opts [String] :last_name surname of the individual - this will be abridged to the first 5 letters only (only set for generating)
|
16
|
+
# @option opts [String] :date_of_birth date of birth of the individual (only set for generating)
|
17
|
+
# @option opts [String] :gender gender of the individual stored as M or F (only set for generating)
|
18
|
+
# @option opts [String] :license_numbers license number of the passport (only set for generating)
|
19
|
+
def initialize(params = {})
|
20
|
+
super(params)
|
21
|
+
@driver_number = params[:driver_number]
|
22
|
+
@first_initials = params[:first_initials]
|
23
|
+
@license_numbers = params[:license_numbers]
|
24
|
+
parse if params.key?(:driver_number)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Parses MRZs and updates the instance variables.
|
28
|
+
# Returns true if it was successfuly parsed, false if there was an issue.
|
29
|
+
#
|
30
|
+
# @return [Boolean] whether the passport is valid or not.
|
31
|
+
# @raise [RuntimeError] if there is no MRZ set in the instance.
|
32
|
+
def parse
|
33
|
+
if !@driver_number.nil?
|
34
|
+
result = Parser::DVLA.parse(@driver_number)
|
35
|
+
@last_name = result[:last_name] if result.key?(:last_name)
|
36
|
+
@first_initials = result[:first_initials] if result.key?(:first_initials)
|
37
|
+
@date_of_birth = result[:date_of_birth] if result.key?(:date_of_birth)
|
38
|
+
@license_numbers = result[:license_numbers] if result.key?(:license_numbers)
|
39
|
+
@gender = result[:gender] if result.key?(:gender)
|
40
|
+
@validity = result[:validity]
|
41
|
+
return true if @validity
|
42
|
+
else
|
43
|
+
fail 'No drivers number set to parse.'
|
44
|
+
end
|
45
|
+
|
46
|
+
false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require "identification/parser/parser"
|
2
|
+
|
3
|
+
module Identification
|
4
|
+
module Parser
|
5
|
+
# Represents a DVLA parser.
|
6
|
+
#
|
7
|
+
# @private
|
8
|
+
class DVLA < Parser
|
9
|
+
def self.parse(driver_number)
|
10
|
+
# Remove whitespace
|
11
|
+
driver_number.gsub!(/\s+/, '')
|
12
|
+
|
13
|
+
return { validity: false } if driver_number.length != 18 && driver_number.length != 16
|
14
|
+
|
15
|
+
doc_data = Hash.new(6)
|
16
|
+
|
17
|
+
# Document will be valid unless an error occurs.
|
18
|
+
doc_data[:validity] = true
|
19
|
+
|
20
|
+
# Document may not be valid, but still, continue parsing.
|
21
|
+
doc_data[:validity] = false unless dvla_check_line_checksum(driver_number)
|
22
|
+
|
23
|
+
doc_data[:last_name] = driver_number[0...5].sub(/9+/, '')
|
24
|
+
|
25
|
+
doc_data[:first_initials] = driver_number[11...13].sub(/9+/, '')
|
26
|
+
|
27
|
+
if driver_number.length == 18
|
28
|
+
doc_data[:license_numbers] = driver_number[16...18]
|
29
|
+
else
|
30
|
+
doc_data[:license_numbers] = '00'
|
31
|
+
end
|
32
|
+
|
33
|
+
doc_data[:date_of_birth] = yymmdd_to_ruby_date(driver_number[5...11])
|
34
|
+
|
35
|
+
if driver_number[6] == '5' || driver_number == '6'
|
36
|
+
doc_data[:gender] = 'F'
|
37
|
+
else
|
38
|
+
doc_data[:gender] = 'M'
|
39
|
+
end
|
40
|
+
|
41
|
+
# In case of an invalid date...
|
42
|
+
doc_data[:validity] = false if doc_data[:date_of_birth] == false
|
43
|
+
|
44
|
+
return doc_data
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.yymmdd_to_ruby_date(input_date)
|
48
|
+
# Change the month back (if gender is set to F)
|
49
|
+
input_date[1] = '0' if input_date[1] == '5'
|
50
|
+
input_date[1] = '1' if input_date[1] == '6'
|
51
|
+
|
52
|
+
# Put to YYMMDD format
|
53
|
+
new_date = String.new(input_date)
|
54
|
+
new_date[1] = input_date[5]
|
55
|
+
new_date[2] = input_date[1]
|
56
|
+
new_date[3] = input_date[2]
|
57
|
+
new_date[4] = input_date[3]
|
58
|
+
new_date[5] = input_date[4]
|
59
|
+
super(new_date)
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.dvla_check_line_checksum(driver_number)
|
63
|
+
# Awaiting response from DVLA...
|
64
|
+
# Boilerplate content for now...
|
65
|
+
|
66
|
+
their_check_digit = driver_number[13...16]
|
67
|
+
|
68
|
+
# Returning true until I know how to calculate check digits
|
69
|
+
true
|
70
|
+
end
|
71
|
+
|
72
|
+
private_class_method :dvla_check_line_checksum
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module Identification
|
4
|
+
module Parser
|
5
|
+
# Represents a document parser.
|
6
|
+
# This class carries base methods that are required to process all documents.
|
7
|
+
#
|
8
|
+
# @private
|
9
|
+
# @abstract Subclass and implement a {#parse} method for the specific document type.
|
10
|
+
class Parser
|
11
|
+
def self.yymmdd_to_ruby_date(input_date)
|
12
|
+
# Only putting the last 2 digits of a date has the flaw of not knowing which century.
|
13
|
+
# So I use the current year as a cut off.
|
14
|
+
# Sorry to those over 100! :-(
|
15
|
+
current_year_yy = (Date.today.strftime('%Y'))[2..4]
|
16
|
+
input_year_yy = input_date[0..1]
|
17
|
+
input_month = input_date[2..3]
|
18
|
+
input_day = input_date[4..5]
|
19
|
+
|
20
|
+
begin
|
21
|
+
return Date.parse(input_day + '-' + input_month + '-' + '19' + input_year_yy) if input_year_yy >= current_year_yy
|
22
|
+
rescue
|
23
|
+
return false
|
24
|
+
end
|
25
|
+
|
26
|
+
begin
|
27
|
+
return Date.parse(input_day + '-' + input_month + '-' + '20' + input_year_yy)
|
28
|
+
rescue
|
29
|
+
return false
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private_class_method :yymmdd_to_ruby_date
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require "identification/parser/parser"
|
2
|
+
|
3
|
+
module Identification
|
4
|
+
module Parser
|
5
|
+
# Represents a passport parser.
|
6
|
+
#
|
7
|
+
# @private
|
8
|
+
class Passport < Parser
|
9
|
+
def self.parse(mrz_line_1, mrz_line_2)
|
10
|
+
return { validity: false } if mrz_line_1.length != 44 || mrz_line_2.length != 44
|
11
|
+
|
12
|
+
doc_data = Hash.new(10)
|
13
|
+
|
14
|
+
# Document will be valid unless an error occurs.
|
15
|
+
doc_data[:validity] = true
|
16
|
+
|
17
|
+
# Document may not be valid, but still, continue parsing.
|
18
|
+
doc_data[:validity] = false unless mrz_check_line_checksum(mrz_line_2)
|
19
|
+
|
20
|
+
# Read the first line without chevrons
|
21
|
+
split = mrz_line_1.split(/<+/)
|
22
|
+
|
23
|
+
doc_data[:date_of_birth] = yymmdd_to_ruby_date(mrz_line_2[13...19])
|
24
|
+
doc_data[:expiry_date] = yymmdd_to_ruby_date(mrz_line_2[21...27])
|
25
|
+
|
26
|
+
# In case of an invalid date...
|
27
|
+
if doc_data['date_of_birth'] == false || doc_data['expiry_date'] == false
|
28
|
+
doc_data[:validity] = false
|
29
|
+
end
|
30
|
+
|
31
|
+
doc_data[:issuing_state] = mrz_line_1[2...5].sub(/<+/, '')
|
32
|
+
doc_data[:last_name] = split[1][3..-1]
|
33
|
+
doc_data[:first_names] = split[2..-1]
|
34
|
+
doc_data[:passport_number] = mrz_line_2[0...9]
|
35
|
+
doc_data[:nationality] = mrz_line_2[10...13].sub(/<+/, '')
|
36
|
+
doc_data[:gender] = mrz_line_2[20].sub(/<+/, '')
|
37
|
+
doc_data[:personal_number] = mrz_line_2[28...42].sub(/<+/, '')
|
38
|
+
|
39
|
+
doc_data
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.mrz_check_digit_calculate(str)
|
43
|
+
str = str.strip.upcase
|
44
|
+
values = str.chars.map do |char|
|
45
|
+
case char
|
46
|
+
when '<'
|
47
|
+
0
|
48
|
+
when 'A'..'Z'
|
49
|
+
char.ord - 65 + 10
|
50
|
+
when '0'..'9'
|
51
|
+
char.ord - 48
|
52
|
+
else
|
53
|
+
fail "Unexpected character '#{char}'"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
(values.zip([7, 3, 1].cycle).map { |(v, w)| v * w }.reduce(:+) % 10).to_s
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.mrz_check_line_checksum(mrz_line)
|
60
|
+
# Grabbing the MRZ's check digits
|
61
|
+
doc_check = []
|
62
|
+
doc_check[0] = mrz_line[9].to_s
|
63
|
+
doc_check[1] = mrz_line[19].to_s
|
64
|
+
doc_check[2] = mrz_line[27].to_s
|
65
|
+
doc_check[3] = mrz_line[42].to_s
|
66
|
+
doc_check[4] = mrz_line[43].to_s
|
67
|
+
|
68
|
+
# Calculating our own check digits...
|
69
|
+
our_check = []
|
70
|
+
our_check[0] = mrz_check_digit_calculate(mrz_line[0...9])
|
71
|
+
our_check[1] = mrz_check_digit_calculate(mrz_line[13...19])
|
72
|
+
our_check[2] = mrz_check_digit_calculate(mrz_line[21...27])
|
73
|
+
our_check[3] = mrz_check_digit_calculate(mrz_line[28...42])
|
74
|
+
our_check[4] = mrz_check_digit_calculate(mrz_line[0...10] + mrz_line[13...20] + mrz_line[21...43])
|
75
|
+
|
76
|
+
# The 4th check digit can be either > or 0, we always return 0 from our CheckDigit calc.
|
77
|
+
our_check[3] = '<' if our_check[3] == '0' && doc_check[3] == '<'
|
78
|
+
|
79
|
+
return true if doc_check.uniq.sort == our_check.uniq.sort
|
80
|
+
|
81
|
+
false
|
82
|
+
end
|
83
|
+
|
84
|
+
private_class_method :mrz_check_digit_calculate, :mrz_check_line_checksum
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'identification/document'
|
2
|
+
require 'identification/parser/passport'
|
3
|
+
|
4
|
+
module Identification
|
5
|
+
# Represents a passport with an MRZ or fields that create an MRZ.
|
6
|
+
class Passport < Document
|
7
|
+
attr_accessor :mrz_line_1, :mrz_line_2, :issuing_state, :first_names, :passport_number, :nationality, :expiry_date, :personal_number
|
8
|
+
|
9
|
+
# Creates an instance of a passport.
|
10
|
+
# Will automatically parse if both MRZ lines are given, and will automatically generate if all necessary fields are set.
|
11
|
+
# (all except personal number which is rarely used)
|
12
|
+
#
|
13
|
+
# @param [Hash] details of the paramaters
|
14
|
+
# @option opts [String] :mrz_line_1 first line of the machine readable zone (only set for parsing)
|
15
|
+
# @option opts [String] :mrz_line_2 second line of the machine readable zone (only set for parsing)
|
16
|
+
# @option opts [String] :issuing_state : issuing state of the individual's passport (only set for generating)
|
17
|
+
# @option opts [String] :first_names : array of first names on the passport (only set for generating)
|
18
|
+
# @option opts [String] :last_name surname of the individual (only set for generating)
|
19
|
+
# @option opts [String] :date_of_birth date of birth of the individual (only set for generating)
|
20
|
+
# @option opts [String] :gender gender of the individual (only set for generating)
|
21
|
+
# @option opts [String] :passport_number : passport number of the individual (only set for generating)
|
22
|
+
# @option opts [String] :nationality : nationality of the individual (only set for generating)
|
23
|
+
# @option opts [String] :expiry_date : expiry date of the passport, given as a date object (only set for generating)
|
24
|
+
# @option opts [String] :personal_number : personal number of the individual - optional (only set for generating)
|
25
|
+
def initialize(params = {})
|
26
|
+
super(params)
|
27
|
+
@mrz_line_1 = params[:mrz_line_1]
|
28
|
+
@mrz_line_2 = params[:mrz_line_2]
|
29
|
+
@issuing_state = params[:issuing_state]
|
30
|
+
@first_names = params[:first_names]
|
31
|
+
@passport_number = params[:passport_number]
|
32
|
+
@nationality = params[:nationality]
|
33
|
+
@expiry_date = params[:expiry_date]
|
34
|
+
@personal_number = params[:personal_number]
|
35
|
+
|
36
|
+
parse if params.key?(:mrz_line_1) && params.key?(:mrz_line_2)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Parses MRZs and updates the instance variables.
|
40
|
+
# Returns true if it was successfuly parsed, false if there was an issue.
|
41
|
+
#
|
42
|
+
# @return [Boolean] whether the passport is valid or not.
|
43
|
+
# @raise [RuntimeError] if there is no MRZ set in the instance.
|
44
|
+
def parse
|
45
|
+
if !@mrz_line_1.nil? && !@mrz_line_2.nil?
|
46
|
+
result = Parser::Passport.parse(@mrz_line_1, @mrz_line_2)
|
47
|
+
@issuing_state = result[:issuing_state] if result.key?(:issuing_state)
|
48
|
+
@last_name = result[:last_name] if result.key?(:last_name)
|
49
|
+
@first_names = result[:first_names] if result.key?(:first_names)
|
50
|
+
@passport_number = result[:passport_number] if result.key?(:passport_number)
|
51
|
+
@nationality = result[:nationality] if result.key?(:nationality)
|
52
|
+
@date_of_birth = result[:date_of_birth] if result.key?(:date_of_birth)
|
53
|
+
@gender = result[:gender] if result.key?(:gender)
|
54
|
+
@expiry_date = result[:expiry_date] if result.key?(:expiry_date)
|
55
|
+
@personal_number = result[:personal_number] if result.key?(:personal_number)
|
56
|
+
@validity = result[:validity]
|
57
|
+
return true if @validity
|
58
|
+
else
|
59
|
+
fail 'No MRZ set to parse.'
|
60
|
+
end
|
61
|
+
|
62
|
+
false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: identification
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ruby
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-07-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.10'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.10'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: A suite of tools for verifying and generating identity documents. Support
|
56
|
+
is currently provided for parsing passports, and UK Drivers Liceneses are in development.
|
57
|
+
Generation will be added in the near future.
|
58
|
+
email:
|
59
|
+
- hi@ruby.sh
|
60
|
+
executables: []
|
61
|
+
extensions: []
|
62
|
+
extra_rdoc_files: []
|
63
|
+
files:
|
64
|
+
- ".gitignore"
|
65
|
+
- ".travis.yml"
|
66
|
+
- Gemfile
|
67
|
+
- LICENSE.txt
|
68
|
+
- README.md
|
69
|
+
- Rakefile
|
70
|
+
- bin/console
|
71
|
+
- bin/setup
|
72
|
+
- identification.gemspec
|
73
|
+
- lib/identification.rb
|
74
|
+
- lib/identification/document.rb
|
75
|
+
- lib/identification/dvla.rb
|
76
|
+
- lib/identification/parser/dvla.rb
|
77
|
+
- lib/identification/parser/parser.rb
|
78
|
+
- lib/identification/parser/passport.rb
|
79
|
+
- lib/identification/passport.rb
|
80
|
+
- lib/identification/version.rb
|
81
|
+
homepage: https://github.com/rubymeow/identification
|
82
|
+
licenses:
|
83
|
+
- MIT
|
84
|
+
metadata: {}
|
85
|
+
post_install_message:
|
86
|
+
rdoc_options: []
|
87
|
+
require_paths:
|
88
|
+
- lib
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
requirements: []
|
100
|
+
rubyforge_project:
|
101
|
+
rubygems_version: 2.4.8
|
102
|
+
signing_key:
|
103
|
+
specification_version: 4
|
104
|
+
summary: A suite of tools for working with various identity documents.
|
105
|
+
test_files: []
|