identikey 0.3.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.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +180 -0
- data/Rakefile +8 -0
- data/bin/console +10 -0
- data/bin/setup +8 -0
- data/identikey.gemspec +34 -0
- data/lib/identikey/administration/digipass.rb +103 -0
- data/lib/identikey/administration/session.rb +119 -0
- data/lib/identikey/administration/session_query.rb +35 -0
- data/lib/identikey/administration/user.rb +127 -0
- data/lib/identikey/administration.rb +211 -0
- data/lib/identikey/authentication.rb +56 -0
- data/lib/identikey/base.rb +272 -0
- data/lib/identikey/unsigned.rb +28 -0
- data/lib/identikey/version.rb +3 -0
- data/lib/identikey.rb +10 -0
- data/log/.keep +0 -0
- metadata +176 -0
@@ -0,0 +1,211 @@
|
|
1
|
+
require 'identikey/base'
|
2
|
+
require 'identikey/administration/session'
|
3
|
+
require 'identikey/administration/session_query'
|
4
|
+
require 'identikey/administration/digipass'
|
5
|
+
require 'identikey/administration/user'
|
6
|
+
|
7
|
+
module Identikey
|
8
|
+
# This class wraps the Administration API wsdl, that contains dozens of
|
9
|
+
# methods. It is currently monolithic.
|
10
|
+
#
|
11
|
+
# It's the lower level into the Administration API, while its models are
|
12
|
+
# wrapped in separate clasess.
|
13
|
+
#
|
14
|
+
class Administration < Base
|
15
|
+
client wsdl: './sdk/wsdl/administration.wsdl'
|
16
|
+
|
17
|
+
operations :logon, :logoff, :sessionalive,
|
18
|
+
:admin_session_query, :user_execute,
|
19
|
+
:digipass_execute, :digipassappl_execute
|
20
|
+
|
21
|
+
def logon(username:, password:, domain:)
|
22
|
+
resp = super(message: {
|
23
|
+
attributeSet: {
|
24
|
+
attributes: typed_attributes_list_from(
|
25
|
+
CREDFLD_DOMAIN: domain,
|
26
|
+
CREDFLD_PASSWORD: password,
|
27
|
+
CREDFLD_USERID: username,
|
28
|
+
CREDFLD_PASSWORD_FORMAT: Unsigned(0)
|
29
|
+
)
|
30
|
+
}
|
31
|
+
})
|
32
|
+
|
33
|
+
parse_response resp, :logon_response
|
34
|
+
end
|
35
|
+
|
36
|
+
def logoff(session_id:)
|
37
|
+
resp = super(message: {
|
38
|
+
attributeSet: {
|
39
|
+
attributes: typed_attributes_list_from(
|
40
|
+
CREDFLD_SESSION_ID: session_id
|
41
|
+
)
|
42
|
+
}
|
43
|
+
})
|
44
|
+
|
45
|
+
parse_response resp, :logoff_response
|
46
|
+
end
|
47
|
+
|
48
|
+
def sessionalive(session_id:)
|
49
|
+
resp = super(message: {
|
50
|
+
attributeSet: {
|
51
|
+
attributes: typed_attributes_list_from(
|
52
|
+
CREDFLD_SESSION_ID: session_id
|
53
|
+
)
|
54
|
+
}
|
55
|
+
})
|
56
|
+
|
57
|
+
parse_response resp, :sessionalive_response
|
58
|
+
end
|
59
|
+
|
60
|
+
def admin_session_query(session_id:)
|
61
|
+
attributes = [ ]
|
62
|
+
|
63
|
+
# These doesn't seem to work as described by the WSDL.
|
64
|
+
# if q_idx
|
65
|
+
# attributes.push(attributeID: 'ADMINSESSIONFLD_SESSION_IDX',
|
66
|
+
# value: { '@xsi:type': 'xsd:string', content!: q_idx})
|
67
|
+
# end
|
68
|
+
|
69
|
+
# if q_location
|
70
|
+
# attributes.push(attributeID: 'ADMINSESSIONFLD_LOCATION',
|
71
|
+
# value: { '@xsi:type': 'xsd:string', content!: q_location})
|
72
|
+
# end
|
73
|
+
|
74
|
+
# if q_username
|
75
|
+
# attributes.push(attributeID: 'ADMINSESSIONFLD_LOGIN_NAME',
|
76
|
+
# value: { '@xsi:type': 'xsd:string', content!: q_username})
|
77
|
+
# end
|
78
|
+
|
79
|
+
resp = super(message: {
|
80
|
+
sessionID: session_id,
|
81
|
+
attributeSet: {
|
82
|
+
attributes: attributes
|
83
|
+
}
|
84
|
+
# fieldSet: { ... }
|
85
|
+
# queryOptions: { ... }
|
86
|
+
})
|
87
|
+
|
88
|
+
parse_response resp, :admin_session_query_response
|
89
|
+
end
|
90
|
+
|
91
|
+
def user_execute(session_id:, cmd:, attributes: [])
|
92
|
+
resp = super(message: {
|
93
|
+
sessionID: session_id,
|
94
|
+
cmd: cmd,
|
95
|
+
attributeSet: {
|
96
|
+
attributes: attributes
|
97
|
+
}
|
98
|
+
})
|
99
|
+
|
100
|
+
parse_response resp, :user_execute_response
|
101
|
+
end
|
102
|
+
|
103
|
+
def user_execute_VIEW(session_id:, username:, domain:)
|
104
|
+
user_execute(
|
105
|
+
session_id: session_id,
|
106
|
+
cmd: 'USERCMD_VIEW',
|
107
|
+
attributes: typed_attributes_list_from(
|
108
|
+
USERFLD_USERID: username,
|
109
|
+
USERFLD_DOMAIN: domain
|
110
|
+
)
|
111
|
+
)
|
112
|
+
end
|
113
|
+
|
114
|
+
def user_execute_CREATE(session_id:, attributes:)
|
115
|
+
user_execute(
|
116
|
+
session_id: session_id,
|
117
|
+
cmd: 'USERCMD_CREATE',
|
118
|
+
attributes: typed_attributes_list_from(attributes)
|
119
|
+
)
|
120
|
+
end
|
121
|
+
|
122
|
+
def user_execute_UPDATE(session_id:, attributes:)
|
123
|
+
user_execute(
|
124
|
+
session_id: session_id,
|
125
|
+
cmd: 'USERCMD_UPDATE',
|
126
|
+
attributes: typed_attributes_list_from(attributes)
|
127
|
+
)
|
128
|
+
end
|
129
|
+
|
130
|
+
def user_execute_DELETE(session_id:, username:, domain:)
|
131
|
+
user_execute(
|
132
|
+
session_id: session_id,
|
133
|
+
cmd: 'USERCMD_DELETE',
|
134
|
+
attributes: typed_attributes_list_from(
|
135
|
+
USERFLD_USERID: username,
|
136
|
+
USERFLD_DOMAIN: domain
|
137
|
+
)
|
138
|
+
)
|
139
|
+
end
|
140
|
+
|
141
|
+
def digipass_execute(session_id:, cmd:, attributes: [])
|
142
|
+
resp = super(message: {
|
143
|
+
sessionID: session_id,
|
144
|
+
cmd: cmd,
|
145
|
+
attributeSet: {
|
146
|
+
attributes: attributes
|
147
|
+
}
|
148
|
+
})
|
149
|
+
|
150
|
+
parse_response resp, :digipass_execute_response
|
151
|
+
end
|
152
|
+
|
153
|
+
def digipass_execute_VIEW(session_id:, serial_no:)
|
154
|
+
digipass_execute(
|
155
|
+
session_id: session_id,
|
156
|
+
cmd: 'DIGIPASSCMD_VIEW',
|
157
|
+
attributes: typed_attributes_list_from(
|
158
|
+
DIGIPASSFLD_SERNO: serial_no
|
159
|
+
)
|
160
|
+
)
|
161
|
+
end
|
162
|
+
|
163
|
+
def digipass_execute_UNASSIGN(session_id:, serial_no:)
|
164
|
+
digipass_execute(
|
165
|
+
session_id: session_id,
|
166
|
+
cmd: 'DIGIPASSCMD_UNASSIGN',
|
167
|
+
attributes: typed_attributes_list_from(
|
168
|
+
DIGIPASSFLD_SERNO: serial_no
|
169
|
+
)
|
170
|
+
)
|
171
|
+
end
|
172
|
+
|
173
|
+
def digipass_execute_ASSIGN(session_id:, serial_no:, username:, domain:, grace_period: 0)
|
174
|
+
digipass_execute(
|
175
|
+
session_id: session_id,
|
176
|
+
cmd: 'DIGIPASSCMD_ASSIGN',
|
177
|
+
attributes: typed_attributes_list_from(
|
178
|
+
DIGIPASSFLD_SERNO: serial_no,
|
179
|
+
DIGIPASSFLD_ASSIGNED_USERID: username,
|
180
|
+
DIGIPASSFLD_DOMAIN: domain,
|
181
|
+
DIGIPASSFLD_GRACE_PERIOD_DAYS: grace_period
|
182
|
+
)
|
183
|
+
)
|
184
|
+
end
|
185
|
+
|
186
|
+
def digipassappl_execute(session_id:, cmd:, attributes:)
|
187
|
+
resp = super(message: {
|
188
|
+
sessionID: session_id,
|
189
|
+
cmd: cmd,
|
190
|
+
attributeSet: {
|
191
|
+
attributes: attributes
|
192
|
+
}
|
193
|
+
})
|
194
|
+
|
195
|
+
parse_response resp, :digipassappl_execute_response
|
196
|
+
end
|
197
|
+
|
198
|
+
def digipassappl_execute_TEST_OTP(session_id:, serial_no:, appl:, otp:)
|
199
|
+
digipassappl_execute(
|
200
|
+
session_id: session_id,
|
201
|
+
cmd: 'DIGIPASSAPPLCMD_TEST_OTP',
|
202
|
+
attributes: typed_attributes_list_from(
|
203
|
+
DIGIPASSAPPLFLD_SERNO: serial_no,
|
204
|
+
DIGIPASSAPPLFLD_APPL_NAME: appl,
|
205
|
+
DIGIPASSAPPLFLD_RESPONSE: otp
|
206
|
+
)
|
207
|
+
)
|
208
|
+
end
|
209
|
+
|
210
|
+
end
|
211
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'identikey/base'
|
2
|
+
|
3
|
+
module Identikey
|
4
|
+
class Authentication < Base
|
5
|
+
client wsdl: './sdk/wsdl/authentication.wsdl'
|
6
|
+
|
7
|
+
operations :auth_user
|
8
|
+
|
9
|
+
def auth_user(user, domain, otp)
|
10
|
+
resp = super(message: {
|
11
|
+
credentialAttributeSet: {
|
12
|
+
attributes: typed_attributes_list_from(
|
13
|
+
CREDFLD_COMPONENT_TYPE: 'Administration Program',
|
14
|
+
CREDFLD_USERID: user,
|
15
|
+
CREDFLD_DOMAIN: domain,
|
16
|
+
CREDFLD_PASSWORD_FORMAT: Unsigned(0),
|
17
|
+
CREDFLD_PASSWORD: otp
|
18
|
+
)
|
19
|
+
}
|
20
|
+
})
|
21
|
+
|
22
|
+
parse_response resp, :auth_user_response
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.valid_otp?(user, domain, otp)
|
26
|
+
status, result, _ = new.auth_user(user, domain, otp)
|
27
|
+
return otp_validated_ok?(status, result)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.validate!(user, domain, otp)
|
31
|
+
status, result, _ = new.auth_user(user, domain, otp)
|
32
|
+
|
33
|
+
if otp_validated_ok?(status, result)
|
34
|
+
return true
|
35
|
+
else
|
36
|
+
error_message = result['CREDFLD_STATUS_MESSAGE']
|
37
|
+
raise Identikey::Error, "OTP Validation error (#{status}): #{error_message}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Given an authentication status and result message, returns true if
|
42
|
+
# that defines a successful OTP validation or not.
|
43
|
+
#
|
44
|
+
# For all cases, except where the OTP is "push", Identikey returns a
|
45
|
+
# status that is != than `STAT_SUCCESS`. But when the OTP is "push",
|
46
|
+
# then Identikey returns a `STAT_SUCCESS` with a "password is wrong"
|
47
|
+
# message in the `CREDFLD_STATUS_MESSAGE`.
|
48
|
+
#
|
49
|
+
# This method checks for both cases.. Success means a `STAT_SUCCESS`
|
50
|
+
# and nothing in the `CREDFLD_STATUS_MESSAGE`.
|
51
|
+
#
|
52
|
+
def self.otp_validated_ok?(status, result)
|
53
|
+
status == 'STAT_SUCCESS' && !result.key?('CREDFLD_STATUS_MESSAGE')
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,272 @@
|
|
1
|
+
module Identikey
|
2
|
+
|
3
|
+
class Base
|
4
|
+
extend Savon::Model
|
5
|
+
|
6
|
+
def self.configure(&block)
|
7
|
+
self.client.globals.instance_eval(&block)
|
8
|
+
|
9
|
+
# Work around a sillyness in Savon
|
10
|
+
if client.globals[:wsdl] != client.wsdl.document
|
11
|
+
client.wsdl.document = client.globals[:wsdl]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.client(options = nil)
|
16
|
+
return super() unless options
|
17
|
+
|
18
|
+
options = DEFAULTS.merge(options)
|
19
|
+
options = process_identikey_filters(options)
|
20
|
+
|
21
|
+
super options
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.default_user_agent_header
|
25
|
+
{'User-Agent' => "ruby/identikey #{Identikey::VERSION}"}
|
26
|
+
end
|
27
|
+
|
28
|
+
# Loops over the filters option content and adds Identikey
|
29
|
+
# specific parameter filtering.
|
30
|
+
#
|
31
|
+
# Due to faulty design in the Identikey SOAP endpoint, the
|
32
|
+
# parameter filters require context-dependant logic as all
|
33
|
+
# attributes are passed in `<attributeID>` elements, while
|
34
|
+
# all values are passed in `<value>` elements.
|
35
|
+
#
|
36
|
+
# Identikey attributes to filter out are specified in the
|
37
|
+
# `filters` option with the `identikey:` prefix.
|
38
|
+
#
|
39
|
+
# Example, filter out the `CREDFLD_PASSWORD` field from
|
40
|
+
# the logs (done by default):
|
41
|
+
#
|
42
|
+
# configure do
|
43
|
+
# filters [ 'identikey:CREDFLD_PASSWORD' ]
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
def self.process_identikey_filters(options)
|
47
|
+
filters = options[:filters] || []
|
48
|
+
|
49
|
+
options[:filters] = filters.map do |filter|
|
50
|
+
if filter.to_s =~ /^identikey:(.+)/
|
51
|
+
filter = identikey_filter_proc_for($1)
|
52
|
+
end
|
53
|
+
|
54
|
+
filter
|
55
|
+
end
|
56
|
+
|
57
|
+
return options
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.identikey_filter_proc_for(attribute)
|
61
|
+
lambda do |document|
|
62
|
+
document.xpath("//attributeID[text()='#{attribute}']/../value").each do |node|
|
63
|
+
node.content = '***FILTERED***'
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
DEFAULTS = {
|
69
|
+
endpoint: 'https://localhost:8888/',
|
70
|
+
|
71
|
+
ssl_version: :TLSv1_2,
|
72
|
+
ssl_verify_mode: :none,
|
73
|
+
|
74
|
+
headers: default_user_agent_header,
|
75
|
+
|
76
|
+
encoding: 'UTF-8',
|
77
|
+
|
78
|
+
logger: Logger.new('log/identikey.log'),
|
79
|
+
log_level: :debug,
|
80
|
+
log: true,
|
81
|
+
pretty_print_xml: true,
|
82
|
+
|
83
|
+
filters: [
|
84
|
+
'identikey:CREDFLD_PASSWORD',
|
85
|
+
'identikey:CREDFLD_STATIC_PASSWORD',
|
86
|
+
'identikey:CREDFLD_SESSION_ID'
|
87
|
+
]
|
88
|
+
}.freeze
|
89
|
+
|
90
|
+
def endpoint
|
91
|
+
self.class.client.globals[:endpoint]
|
92
|
+
end
|
93
|
+
|
94
|
+
def wsdl
|
95
|
+
self.class.client.globals[:wsdl]
|
96
|
+
end
|
97
|
+
|
98
|
+
protected
|
99
|
+
|
100
|
+
# Parse the generic response types that the API returns.
|
101
|
+
#
|
102
|
+
# The returned attributes (up to now...) are always:
|
103
|
+
#
|
104
|
+
# - The given root element, whose name is derived from the SOAP command
|
105
|
+
# that was invoked
|
106
|
+
# - The :results element, containing:
|
107
|
+
# - :result_codes, containing :status_code_enum that is the operation
|
108
|
+
# result code
|
109
|
+
# - :result_attribute, that may either contain a single attributes list
|
110
|
+
# or multiple ones.
|
111
|
+
# - :error_stack, a list of error that occurred
|
112
|
+
#
|
113
|
+
# The returned value is a three-elements array, containing:
|
114
|
+
#
|
115
|
+
# [Response code, Attribute(s) list, Errors list]
|
116
|
+
#
|
117
|
+
# The response code is a string from the IDENTIKEY Authentication Server
|
118
|
+
# Error Codes table.
|
119
|
+
#
|
120
|
+
# The attributes list is an Hash when a single object's attributes were
|
121
|
+
# requested, or is an Array of Hashes when the response contains a list
|
122
|
+
# of objects.
|
123
|
+
#
|
124
|
+
# The attributes list may be nil.
|
125
|
+
#
|
126
|
+
# The errors list is an array of strings containing error descriptions.
|
127
|
+
# The strings themselves contain the error code, albeit in different
|
128
|
+
# formats. TODO maybe create a separate class for errors, that includes
|
129
|
+
# the error code.
|
130
|
+
#
|
131
|
+
# TODO refactor and split in separate methods
|
132
|
+
#
|
133
|
+
def parse_response(resp, root_element)
|
134
|
+
body = resp.body
|
135
|
+
|
136
|
+
if body.size.zero?
|
137
|
+
raise Identikey::Error, "Empty response received"
|
138
|
+
end
|
139
|
+
|
140
|
+
unless body.key?(root_element)
|
141
|
+
raise Identikey::Error, "Expected response to have #{root_element}, found #{body.keys.join(', ')}"
|
142
|
+
end
|
143
|
+
|
144
|
+
# The root results element
|
145
|
+
#
|
146
|
+
root = body[root_element]
|
147
|
+
|
148
|
+
# ... that the authentication API wraps with another element
|
149
|
+
#
|
150
|
+
results_key = root_element.to_s.sub(/_response$/, '_results').to_sym
|
151
|
+
if root.keys.size == 1 && root.key?(results_key)
|
152
|
+
root = root[results_key]
|
153
|
+
end
|
154
|
+
|
155
|
+
# The results element
|
156
|
+
#
|
157
|
+
unless root.key?(:results)
|
158
|
+
raise Identikey::Error, "Results element not found below #{root_element}"
|
159
|
+
end
|
160
|
+
|
161
|
+
results = root[:results]
|
162
|
+
|
163
|
+
# Result code
|
164
|
+
#
|
165
|
+
unless results.key?(:result_codes)
|
166
|
+
raise Identikey::Error, "Result codes not found below #{root_element}"
|
167
|
+
end
|
168
|
+
|
169
|
+
result_code = results[:result_codes][:status_code_enum] || 'STAT_UNKNOWN'
|
170
|
+
|
171
|
+
# Result attributes
|
172
|
+
#
|
173
|
+
unless results.key?(:result_attribute)
|
174
|
+
raise Identikey::Error, "Result attribute not found below #{root_element}"
|
175
|
+
end
|
176
|
+
|
177
|
+
results_attr = results[:result_attribute]
|
178
|
+
|
179
|
+
result_attributes = if results_attr.key?(:attributes)
|
180
|
+
entries = [ results_attr[:attributes] ].flatten
|
181
|
+
parse_attributes entries
|
182
|
+
|
183
|
+
elsif results_attr.key?(:attribute_list)
|
184
|
+
# This attribute may contain a single entry or multiple ones. Lists of
|
185
|
+
# a single element are returned as a single attributes set.. but the
|
186
|
+
# caller expects a list so we return the single element in an Array.
|
187
|
+
#
|
188
|
+
entries = [ results_attr[:attribute_list] ].flatten
|
189
|
+
entries.inject([]) do |a, entry|
|
190
|
+
a.push parse_attributes(entry[:attributes])
|
191
|
+
end
|
192
|
+
else
|
193
|
+
nil
|
194
|
+
end
|
195
|
+
|
196
|
+
# Errors
|
197
|
+
#
|
198
|
+
errors = if results[:error_stack].key?(:errors)
|
199
|
+
parse_errors results[:error_stack][:errors]
|
200
|
+
else
|
201
|
+
nil
|
202
|
+
end
|
203
|
+
|
204
|
+
return result_code, result_attributes, errors
|
205
|
+
end
|
206
|
+
|
207
|
+
def parse_attributes(attributes)
|
208
|
+
attributes.inject({}) do |h, attribute|
|
209
|
+
h.update(attribute.fetch(:attribute_id) => attribute.fetch(:value))
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def parse_errors(errors)
|
214
|
+
case errors
|
215
|
+
when Array
|
216
|
+
errors.map { |e| e.fetch(:error_desc) }
|
217
|
+
when Hash
|
218
|
+
errors.fetch(:error_desc)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# Converts and hash keyed by attribute name into an array of hashes
|
223
|
+
# whose keys are the attribute name as attributeID and the value as
|
224
|
+
# a Gyoku-compatible hash with the xsd:type annotation. The type is
|
225
|
+
# inferred from the Ruby value type and the contents are serialized
|
226
|
+
# as a string formatted as per the XSD DTD definition.
|
227
|
+
#
|
228
|
+
# <rant>
|
229
|
+
# This code should not exist, because defining argument types is what
|
230
|
+
# WSDL is for. However, in the braindead web services implementation
|
231
|
+
# of Vasco there are infinite protocols that accept a variable number
|
232
|
+
# of attributes and their types are defined only in the documentation
|
233
|
+
# and in server code, making WSDL (and SOAP) only an annoynace rather
|
234
|
+
# than an aid.
|
235
|
+
# </rant>
|
236
|
+
#
|
237
|
+
def typed_attributes_list_from(hash)
|
238
|
+
hash.map do |name, value|
|
239
|
+
type, value = case value
|
240
|
+
|
241
|
+
when Unsigned
|
242
|
+
[ 'xsd:unsignedInt', value.to_s ]
|
243
|
+
|
244
|
+
when Integer
|
245
|
+
[ 'xsd:int', value.to_s ]
|
246
|
+
|
247
|
+
when DateTime, Time
|
248
|
+
[ 'xsd:datetime', value.utc.iso8601 ]
|
249
|
+
|
250
|
+
when TrueClass, FalseClass
|
251
|
+
[ 'xsd:boolean', value.to_s ]
|
252
|
+
|
253
|
+
when Symbol, String
|
254
|
+
[ 'xsd:string', value.to_s ]
|
255
|
+
|
256
|
+
when NilClass
|
257
|
+
next
|
258
|
+
|
259
|
+
else
|
260
|
+
raise Identikey::Error, "#{name} type #{value.class} is unsupported"
|
261
|
+
end
|
262
|
+
|
263
|
+
{ attributeID: name.to_s,
|
264
|
+
value: { '@xsi:type': type, content!: value } }
|
265
|
+
end.compact
|
266
|
+
end
|
267
|
+
|
268
|
+
# protected
|
269
|
+
|
270
|
+
end
|
271
|
+
|
272
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#
|
2
|
+
# Wrapper for an integer immediate value, that is used only as an annotation
|
3
|
+
# for typed_attributes_list_from() in order to generate the correct XSD type
|
4
|
+
# from an Object's class.
|
5
|
+
#
|
6
|
+
class Unsigned < BasicObject
|
7
|
+
def initialize(value)
|
8
|
+
@int = ::Kernel::Integer(value)
|
9
|
+
|
10
|
+
if @int < 0
|
11
|
+
raise ArgumentError, "Invalid input syntax for Unsigned integer: #{value}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def class
|
16
|
+
::Unsigned
|
17
|
+
end
|
18
|
+
|
19
|
+
def method_missing(meth, *args, &block)
|
20
|
+
@int.public_send(meth, *args, &block)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module Kernel
|
25
|
+
def Unsigned(value)
|
26
|
+
::Unsigned.new(value)
|
27
|
+
end
|
28
|
+
end
|
data/lib/identikey.rb
ADDED
data/log/.keep
ADDED
File without changes
|