kdonovan-trufina 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ rdoc/
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 [name of plugin creator]
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,116 @@
1
+ = Trufina
2
+
3
+ The Trufina gem provides a DSL allowing you to easily interface with {Trufina.com}[http://www.trufina.com]'s identity verification API.
4
+ Trufina[http://www.trufina.com] provides an identity verification service, and this DSL basically lets you request verified information
5
+ from the user (who provides that information directly to Trufina[http://www.trufina.com], and then uses their website to control
6
+ permissions of who can access what parts of their personal data).
7
+
8
+ == Requirements
9
+
10
+ Before you begin you'll need to fill out some paperwork with Trufina[http://www.trufina.com] and, after a bit of administrative mucking around,
11
+ you'll be given two important identifiers: a PID (Partner ID) and a PAK (Partner Authentication Key). Place these in
12
+ your +config/trufina.yml+ file, replace the other defaults, and you'll be good to go.
13
+
14
+ == Installation
15
+
16
+ Getting the code on your system is as simple as
17
+
18
+ script/plugin install git://github.com/kdonovan/trufina.git
19
+
20
+ or
21
+
22
+ gem sources -a http://gems.github.com
23
+ sudo gem install kdonovan-trufina
24
+
25
+
26
+ Once you have the code, you'll need to create a +trufina.yml+ file in your project's config directory. If you don't
27
+ do this by hand, a template file will be created automatically the first time you load your application after installation.
28
+ Trufina will raise a ConfigFileError until you fill out the config file with meaningful data.
29
+
30
+
31
+ == Example
32
+
33
+ Once installation has been completed, using the code itself is really easy -- the most complicated step is understanding
34
+ Trufina[http://www.trufina.com]'s various flows. We'll walk through an example:
35
+
36
+ # We'll skip it here for verbosity, but note that you can turn on debugging
37
+ # to print out pretty versions of any XML sent or received.
38
+ # Trufina::Config.debug = true
39
+
40
+ Say we have a user in our database for whom we want verified information. The first step is to
41
+ establish a session key with Trufina[http://www.trufina.com] so we can associate later responses with this request. This
42
+ is done by sending them a PRT (Partner Request Token), which can be any arbitrary value. In a real app
43
+ I'd probably use a user-id + timestamp combination, but for this demo we'll use the {random number}[http://xkcd.com/221/] 4.
44
+
45
+ Note that you can also specify what data you want access to (name, address, country of birth, etcetera),
46
+ as well as any default values you may already know to prefill the form on the Trufina[http://www.trufina.com] website. See
47
+ Trufina.login_url or Trufina.login_request for more details, but the default is to request the user's first and last name.
48
+
49
+ Trufina.login_url(4, :demo => true) # => http://staging.trufina.com/DemoPartnerLogin/DemoLogin/6ZEeENWWD8@K
50
+
51
+
52
+ You can now visit this URL to create an account for a fake user on Trufina[http://www.trufina.com]'s demo server. When you're done,
53
+ you'll be redirected to whatever you put as your success endpoint in +trufina.yml+, and there will be a TLID
54
+ (Temporary Login ID) appended. In my case it was 870. You have 15 minutes to use this TLID to access the
55
+ information about the user Trufina[http://www.trufina.com] has verified.
56
+
57
+ info = Trufina.login_info_request(870)
58
+ info.data.present_and_verified # => [{:name=>[{:first=>"TEST_FIRNAME"}, {:surname=>"TEST_SURNAME"}]}] (or whatever names you entered on the staging server)
59
+
60
+
61
+ That completes the simplest use-case. Say we decide we want more information about the user, like their middle
62
+ name. We send Trufina[http://www.trufina.com] an access request, and if the user has already provided the information and given us
63
+ permission we'll get the info back immediately.
64
+
65
+ new_info = Trufina.access_request(info, {:name => [:middle]})
66
+ new_info.data.present_and_verified # => [{:name=>[{:middle=>"SomeMiddleName"}]}]
67
+
68
+
69
+ Note that here our demo user has already given Trufina[http://www.trufina.com] permission to see all the name components, so we get
70
+ the middle name back immediately. For a different component where Trufina[http://www.trufina.com] needs to ask the user there's no
71
+ useful data in the response (the XML contains a "pending" node, but the gem doesn't bother parsing it out).
72
+
73
+ Trufina.access_request(info, [:phone]).data.present_and_verified # => []
74
+
75
+
76
+ In this case we would receive an AccessNotification to our servers once the user gave us permission to access
77
+ the requested data, and at that point we'd be able to re-request the data with another Trufina.access_request
78
+ call, for which the newly requested data would show up like the middle name did above.
79
+
80
+
81
+ == Advanced Topics
82
+
83
+ === Seed Info
84
+
85
+ Trufina.login_request (and therefore Trufina.login_url, which is just a wrapper) allows you to pass along seed
86
+ data used to prepopulate the fields the user will encounter on Trufina.com (see Trufina::Elements::SeedInfoGroup
87
+ for all options). Prepopulated data will be specified as the value of a :seed key in the options hash of either
88
+ method. Example:
89
+
90
+ Trufina.login_request(4, :seed => {:name => {:first => 'Foo', :surname => 'Bar'}})
91
+
92
+ === Request Data
93
+
94
+ A number of the API calls allow you to supply a list of the data you'd like returned about the user in question.
95
+ You may do this as follows:
96
+
97
+ Trufina.login_request(4, :requested => [:age, {:name => [:first, :middle, :surname]}])
98
+
99
+ or
100
+
101
+ Trufina.access_request({:pur => 4, :prt => 4}, [:age, {:name => [:first, :middle, :surname]}])
102
+
103
+
104
+ == Unsupported functionality
105
+
106
+ * Does not handle requesting comparisons or other request attributes
107
+ * (Note that Trufina[http://www.trufina.com] itself doesn't support maxAge or timeframe yet)
108
+ * Setting ResidenceAddress seed data isn't yet supported
109
+
110
+ == Compatibility
111
+
112
+ The goal of this module is to be relatively framework agnostic. That being said, I've personally only tried to use it
113
+ in conjunction with a Rails application. If you run into problems trying to use it elsewhere, well... patches happily
114
+ accepted. :)
115
+
116
+ Copyright (c) 2009 Kali Donovan, released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gem|
8
+ gem.name = "trufina"
9
+ gem.summary = %Q{DSL to easily interact with Trufina's verification API}
10
+ gem.description = %Q{Provides a DSL to easily interact with the XML API offered by Trufina.com, an identity verification company.}
11
+ gem.email = "kali.donovan@gmail.com"
12
+ gem.homepage = "http://github.com/kdonovan/trufina"
13
+ gem.authors = ["Kali Donovan"]
14
+ gem.add_dependency "jimmyz-happymapper"
15
+ end
16
+ rescue LoadError
17
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
18
+ end
19
+
20
+ desc 'Default: run unit tests.'
21
+ task :default => :test
22
+
23
+ desc 'Test the trufina plugin.'
24
+ Rake::TestTask.new(:test) do |t|
25
+ t.libs << 'lib'
26
+ t.libs << 'test'
27
+ t.pattern = 'test/**/*_test.rb'
28
+ t.verbose = true
29
+ end
30
+
31
+ desc 'Generate documentation for the trufina plugin.'
32
+ Rake::RDocTask.new(:rdoc) do |rdoc|
33
+ rdoc.rdoc_dir = 'rdoc'
34
+ rdoc.title = 'Trufina API'
35
+ rdoc.options << '--line-numbers' << '--inline-source'
36
+ rdoc.rdoc_files.include('README.rdoc')
37
+ rdoc.rdoc_files.include('lib/**/*.rb')
38
+ rdoc.rdoc_files.exclude('init.rb')
39
+ rdoc.rdoc_files.exclude('rails/init.rb')
40
+ end
41
+
42
+ # gem 'darkfish-rdoc'
43
+ # require 'darkfish-rdoc'
44
+ #
45
+ # Rake::RDocTask.new(:darkfish) do |rdoc|
46
+ # rdoc.title = "Trufina API"
47
+ # rdoc.rdoc_files.include 'README.rdoc'
48
+ #
49
+ # rdoc.options += [
50
+ # '-SHN',
51
+ # '-f', 'darkfish', # This is the important bit
52
+ # ]
53
+ # end
data/TODO ADDED
@@ -0,0 +1,8 @@
1
+ PENDING:
2
+ * Schema validation against the provided .xsd fails
3
+ * Unable to set ResidenceAddress in seed data (e.g. Trufina.login_request(4, :seed => {:residence_address => {:state => 'CA'}}))
4
+ Yields error: Attribute 'timeframe' must appear on element 'ResidenceAddress' (noted in README)
5
+
6
+ UNSUPPORTED:
7
+ * Setting StreetAddress seed data is unsupported (will generate extra <.> tags, and it is unclear how to set multiple lines)
8
+ * Comparison or other request attributes (note that maxAge and timeframe are not even supported by Trufina yet)
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), 'rails', 'init')
data/lib/config.rb ADDED
@@ -0,0 +1,62 @@
1
+ class Trufina
2
+ # Reads in configuration data from config/trufina.yml (and handles creating it if missing / complaining if it looks unfilled).
3
+ class Config
4
+ cattr_accessor :credentials, :staging_access, :endpoints,
5
+ :app_root, :config_file, :mode, :debug
6
+
7
+ # Allow range of config locations
8
+ self.app_root = RAILS_ROOT if defined?(RAILS_ROOT)
9
+ self.app_root = Merb.root if defined?(Merb)
10
+ self.app_root ||= app_root
11
+ self.app_root ||= Dir.pwd
12
+ self.config_file = File.join(self.app_root, 'config', 'trufina.yml')
13
+
14
+ # Symbolize hash keys - defined here so we don't rely on Rails
15
+ def self.symbolize_keys!(hash) # :nodoc:
16
+ return hash unless hash.is_a?(Hash)
17
+
18
+ hash.keys.each do |key|
19
+ unless key.is_a?(Symbol)
20
+ hash[key.to_sym] = hash[key]
21
+ hash.delete(key)
22
+ end
23
+ end
24
+ hash
25
+ end
26
+
27
+ # Ensure config exists
28
+ unless File.exists?(self.config_file)
29
+ config_template = File.join(File.dirname(__FILE__), '..', 'trufina.yml.template')
30
+ File.copy(config_template, self.config_file)
31
+ raise Exceptions::ConfigFileError.new("Unable to create configuration template at #{self.config_file}") unless File.exists?(self.config_file)
32
+ end
33
+
34
+ # Load keys from config file into the class
35
+ YAML.load(ERB.new(File.read(self.config_file)).result).each do |key, value|
36
+ self.send("#{key}=", symbolize_keys!(value)) if self.methods.include?("#{key}=")
37
+ end
38
+
39
+ # Set default mode unless already set in the config file
40
+ unless %w(production staging).include?(self.mode)
41
+ env = defined?(Merb) ? ENV['MERB_ENV'] : ENV['RAILS_ENV']
42
+ @@mode = env && env == 'production' ? 'production' : 'staging'
43
+ end
44
+
45
+ # Ensure template file has been modified with (hopefully) real data
46
+ if self.credentials.any?{|k,v| v == 'YOUR_DATA_HERE'}
47
+ raise Exceptions::ConfigFileError.new("Don't forget to update the Trufina config file with your own data! File is located at #{self.config_file}")
48
+ end
49
+
50
+ # Syntactic sugar for setting and checking the current operating mode
51
+ class << self
52
+ %w(staging production).each do |mode|
53
+ define_method("#{mode}!"){ @@mode = mode }
54
+ define_method("#{mode}?"){ @@mode == mode }
55
+ end
56
+ def debug?
57
+ !!@@debug
58
+ end
59
+ end
60
+
61
+ end
62
+ end
data/lib/elements.rb ADDED
@@ -0,0 +1,170 @@
1
+ # Contains smaller classes (essentially HappyMapper element classes) used to create and
2
+ # parse API calls and responses.
3
+
4
+ class Trufina
5
+
6
+ # Handle creating a HappyMapper object from array or hash (creating empty nodes as required).
7
+ module AllowCreationFromHash
8
+
9
+ def initialize(seed_data = {})
10
+ seed_data.is_a?(Array) ? create_empty_nodes(seed_data) : create_nodes(seed_data)
11
+ end
12
+
13
+ protected
14
+
15
+ # e.g. Trufina::Name.new([:first, :suffix]) - no values provided, print empty nodes
16
+ #
17
+ # <Name>
18
+ # <First/>
19
+ # <Suffix/>
20
+ # </Name>
21
+ def create_empty_nodes(nodes)
22
+ nodes.each do |node|
23
+ create_node(node)
24
+ end
25
+ end
26
+
27
+ # e.g. Trufina::Name.new({:first => 'Bob', :suffix => 'III'}) - print nodes with values
28
+ #
29
+ # <Name>
30
+ # <First>Bob</First>
31
+ # <Suffix>III</Suffix>
32
+ # </Name>
33
+ def create_nodes(nodes)
34
+ nodes.each do |node, content|
35
+ create_node(node, content)
36
+ end
37
+ end
38
+
39
+ # Handle the actual node creation
40
+ def create_node(name, content = nil)
41
+ case name
42
+ when Array then create_empty_nodes(name)
43
+ when Hash then create_nodes(name)
44
+ else
45
+ element = self.class.elements.detect{|e| e.method_name.to_sym == name}
46
+ raise Exceptions::InvalidElement.new("No known element named '#{name}'") unless element
47
+
48
+ value = if HappyMapper::Item::Types.include?(element.type)
49
+ content ? content : ''
50
+ else
51
+ content ? element.type.new(content) : element.type.new
52
+ end
53
+ self.send("#{name}=", value)
54
+ end
55
+ end
56
+
57
+ end
58
+
59
+ module Elements
60
+ RESPONSE_XML_ATTRIBUTES = {:state => String, :age => String, :charged => String, :status => String, :errors => String }
61
+
62
+ module EasyElementAccess
63
+
64
+ # Shortcut to collecting any information that's present and available
65
+ def present_and_verified
66
+ yes = []
67
+ self.class.elements.map(&:method_name).each do |p|
68
+ next unless val = self.send(p)
69
+
70
+ if val.respond_to?(:present_and_verified)
71
+ yes << {p.to_sym => val.present_and_verified}
72
+ else
73
+ yes << {p.to_sym => val} if val.state == 'verified' && val.status == 'present'
74
+ end
75
+ end
76
+ yes
77
+ end
78
+
79
+ end
80
+
81
+ # Encapsulates the various name components Trufina accepts
82
+ class Name
83
+ include AllowCreationFromHash
84
+ include HappyMapper
85
+ include EasyElementAccess
86
+ tag 'Name'
87
+
88
+ element :prefix, String, :tag => 'Prefix', :attributes => RESPONSE_XML_ATTRIBUTES
89
+ element :first, String, :tag => 'First', :attributes => RESPONSE_XML_ATTRIBUTES
90
+ element :middle, String, :tag => 'MiddleName', :attributes => RESPONSE_XML_ATTRIBUTES
91
+ element :middle_initial, String, :tag => 'MiddleInitial', :attributes => RESPONSE_XML_ATTRIBUTES
92
+ element :surname, String, :tag => 'Surname', :attributes => RESPONSE_XML_ATTRIBUTES
93
+ element :suffix, String, :tag => 'Suffix', :attributes => RESPONSE_XML_ATTRIBUTES
94
+ end
95
+
96
+ # Wrapper attempting to allow access to multiple StreetAddress tags per single ResidenceAddress
97
+ class StreetAddress
98
+ include AllowCreationFromHash
99
+ include HappyMapper
100
+ include EasyElementAccess
101
+ tag 'StreetAddress'
102
+
103
+ element :name, String, :tag => '.', :attributes => RESPONSE_XML_ATTRIBUTES
104
+ end
105
+
106
+ # Encapsulates Trufina's address fields
107
+ class ResidenceAddress
108
+ include AllowCreationFromHash
109
+ include HappyMapper
110
+ include EasyElementAccess
111
+ tag 'ResidenceAddress'
112
+
113
+ has_many :street_addresses, StreetAddress, :tag => 'StreetAddress', :attributes => RESPONSE_XML_ATTRIBUTES
114
+ element :city, String, :tag => 'City', :attributes => RESPONSE_XML_ATTRIBUTES
115
+ element :state, String, :tag => 'State', :attributes => RESPONSE_XML_ATTRIBUTES
116
+ element :postal_code, String, :tag => 'PostalCode', :attributes => RESPONSE_XML_ATTRIBUTES
117
+ end
118
+
119
+ # Encapsulates all response data Trufina may send back
120
+ class AccessResponseGroup
121
+ include AllowCreationFromHash
122
+ include HappyMapper
123
+ include EasyElementAccess
124
+ tag 'AccessResponse'
125
+
126
+ element :name, Name, :single => true, :attributes => RESPONSE_XML_ATTRIBUTES
127
+ # element :birth_date, Date, :tag => 'DateOfBirth', :attributes => RESPONSE_XML_ATTRIBUTES
128
+ # element :birth_country, String, :tag => 'CountryOfBirth', :attributes => RESPONSE_XML_ATTRIBUTES
129
+ element :phone, String, :tag => 'Phone', :attributes => RESPONSE_XML_ATTRIBUTES
130
+ element :residence_address, ResidenceAddress, :single => true, :attributes => RESPONSE_XML_ATTRIBUTES
131
+ element :ssn, String, :tag => 'fullSSN', :attributes => RESPONSE_XML_ATTRIBUTES
132
+ element :last_4_ssn, String, :tag => 'Last4SSN', :attributes => RESPONSE_XML_ATTRIBUTES
133
+ element :age, String, :tag => 'Age', :attributes => RESPONSE_XML_ATTRIBUTES
134
+ end
135
+
136
+ # Encapsulates all data we can request from Trufina
137
+ class AccessRequest
138
+ include AllowCreationFromHash
139
+ include HappyMapper
140
+ tag 'AccessRequest'
141
+
142
+ element :name, Name, :single => true
143
+ element :birth_date, Date, :tag => 'DateOfBirth'
144
+ element :birth_country, String, :tag => 'CountryOfBirth'
145
+ element :phone, String, :tag => 'Phone' # If Trufina implemented it, could have timeframe and maxAge attributes
146
+ element :residence_address, ResidenceAddress, :single => true # If Trufina implemented it, could have timeframe and maxAge attributes
147
+ element :ssn, String, :tag => 'fullSSN'
148
+ element :last_4_ssn, String, :tag => 'Last4SSN'
149
+ element :age, String, :tag => 'Age', :attributes => {:comparison => String}
150
+ end
151
+
152
+ # Encapsulates all seed data Trufina accepts
153
+ class SeedInfoGroup
154
+ include AllowCreationFromHash
155
+ include HappyMapper
156
+ tag 'SeedInfo'
157
+
158
+ element :name, Name, :single => true
159
+ element :email, String, :single => true
160
+ element :birth_date, Date, :tag => 'DateOfBirth'
161
+ element :birth_country, String, :tag => 'CountryOfBirth'
162
+ element :phone, String, :tag => 'Phone'
163
+ element :residence_address, ResidenceAddress, :single => true
164
+ element :ssn, String, :tag => 'fullSSN'
165
+ element :last_4_ssn, String, :tag => 'Last4SSN'
166
+ element :age, String, :tag => 'Age'
167
+ end
168
+
169
+ end
170
+ end
data/lib/exceptions.rb ADDED
@@ -0,0 +1,15 @@
1
+ # This file defines all Trufina Exceptions
2
+
3
+ class Trufina
4
+ module Exceptions
5
+ class TrufinaException < StandardError; end
6
+ class ConfigFileError < TrufinaException; end
7
+ class MissingToken < TrufinaException; end
8
+ class MissingRequiredElements < TrufinaException; end
9
+ class MissingRequiredAttributes < TrufinaException; end
10
+ class InvalidElement < TrufinaException; end
11
+ class NetworkError < TrufinaException; end
12
+ class UnknownResponseType < TrufinaException; end
13
+ class TrufinaResponseException < TrufinaException; end
14
+ end
15
+ end