kdonovan-trufina 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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