viisp-auth 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.
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VIISP
4
+ module Auth
5
+ class Configuration
6
+ attr_writer :pid
7
+ attr_writer :postback_url
8
+ attr_writer :private_key
9
+ attr_writer :service_cert
10
+ attr_writer :test
11
+ attr_writer :endpoint
12
+ attr_writer :portal_endpoint
13
+
14
+ attr_accessor :providers
15
+ attr_accessor :attributes
16
+ attr_accessor :user_information
17
+
18
+ attr_accessor :read_timeout
19
+ attr_accessor :open_timeout
20
+
21
+ CERTS_PATH = File.expand_path('../../../../certs', __FILE__).freeze
22
+
23
+ DEFAULT_PROVIDERS = %w[
24
+ auth.lt.identity.card
25
+ auth.lt.bank
26
+ auth.signatureProvider
27
+ auth.login.pass
28
+ auth.lt.government.employee.card
29
+ auth.stork
30
+ auth.tsl.identity.card
31
+ ].freeze
32
+
33
+ DEFAULT_ATTRIBUTES = %w[
34
+ lt-personal-code
35
+ lt-company-code
36
+ lt-government-employee-code
37
+ stork-eid
38
+ tsl-serial-number
39
+ login
40
+ ].freeze
41
+
42
+ DEFAULT_USER_INFORMATION = %w[
43
+ firstName
44
+ lastName
45
+ address
46
+ email
47
+ phoneNumber
48
+ birthday
49
+ companyName
50
+ ].freeze
51
+
52
+ PRODUCTION_ENDPOINT = 'https://www.epaslaugos.lt/portal/authenticationServices/auth'
53
+ PRODUCTION_PORTAL_ENDPOINT = 'https://www.epaslaugos.lt/portal/external/services/authentication/v2/'
54
+
55
+ TEST_PID = 'VSID000000000113'
56
+ TEST_ENDPOINT = 'https://www.epaslaugos.lt/portal-test/services/AuthenticationServiceProxy'
57
+ TEST_PORTAL_ENDPOINT = 'https://www.epaslaugos.lt/portal-test/external/services/authentication/v2/'
58
+
59
+ DEFAULT_OPEN_TIMEOUT = 3
60
+ DEFAULT_READ_TIMEOUT = 10
61
+
62
+ def initialize
63
+ @providers = DEFAULT_PROVIDERS
64
+ @attributes = DEFAULT_ATTRIBUTES
65
+ @user_information = DEFAULT_USER_INFORMATION
66
+
67
+ @open_timeout = DEFAULT_OPEN_TIMEOUT
68
+ @read_timeout = DEFAULT_READ_TIMEOUT
69
+ end
70
+
71
+ def pid
72
+ return @pid if @pid
73
+ return TEST_PID if test?
74
+ error('pid not configured')
75
+ end
76
+
77
+ def postback_url
78
+ @postback_url || error('postback_url not configured')
79
+ end
80
+
81
+ def endpoint
82
+ return @endpoint if @endpoint
83
+ return TEST_ENDPOINT if test?
84
+ PRODUCTION_ENDPOINT
85
+ end
86
+
87
+ def portal_endpoint
88
+ return @portal_endpoint if @portal_endpoint
89
+ return TEST_PORTAL_ENDPOINT if test?
90
+ PRODUCTION_PORTAL_ENDPOINT
91
+ end
92
+
93
+ def private_key
94
+ return @private_key if @private_key
95
+ return test_private_key if test?
96
+ error('private key not configured')
97
+ end
98
+
99
+ def service_cert
100
+ @service_cert || builtin_service_cert
101
+ end
102
+
103
+ def test?
104
+ @test
105
+ end
106
+
107
+ private
108
+
109
+ def builtin_service_cert
110
+ @builtin_service_cert ||= OpenSSL::X509::Certificate.new(
111
+ read_cert('epaslaugos_ident.crt')
112
+ )
113
+ end
114
+
115
+ def test_private_key
116
+ @test_private_key ||= OpenSSL::PKey::RSA.new(
117
+ read_cert('testKey.pem')
118
+ )
119
+ end
120
+
121
+ def read_cert(filename)
122
+ path = File.join(CERTS_PATH, filename)
123
+ File.read(path)
124
+ end
125
+
126
+ def error(message)
127
+ raise(ConfigurationError, message)
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VIISP
4
+ module Auth
5
+ class Error < StandardError; end
6
+ class RequestError < Error; end
7
+ class SignatureError < Error; end
8
+ class ConfigurationError < Error; end
9
+ end
10
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VIISP
4
+ module Auth
5
+ class Identity
6
+ attr_reader :doc
7
+
8
+ def initialize(doc)
9
+ @doc = doc
10
+ end
11
+
12
+ def to_hash
13
+ {
14
+ 'authentication_provider' => element_text('authenticationProvider'),
15
+ 'attributes' => attributes,
16
+ 'user_information' => user_information,
17
+ 'custom_data' => element_text('customData'),
18
+ 'source_data' => source_data,
19
+ }
20
+ end
21
+
22
+ private
23
+
24
+ def attributes
25
+ pairs = doc.css('authenticationAttribute').map do |el|
26
+ [el.at('attribute').text, el.at('value').text]
27
+ end
28
+
29
+ Hash[pairs]
30
+ end
31
+
32
+ def user_information
33
+ pairs = doc.css('userInformation').map do |el|
34
+ value = el.at('stringValue')&.text || el.at('dateValue')&.text
35
+ [el.at('information').text, value]
36
+ end
37
+
38
+ Hash[pairs]
39
+ end
40
+
41
+ def source_data
42
+ return unless source_data_element
43
+
44
+ {
45
+ 'type' => source_data_element.at('type').text,
46
+ 'parameters' => source_data_parameters,
47
+ }
48
+ end
49
+
50
+ def source_data_element
51
+ @source_data_element ||= doc.at('sourceData')
52
+ end
53
+
54
+ def source_data_parameters
55
+ pairs = source_data_element.css('parameter').map do |el|
56
+ [el.attr('name'), el.text]
57
+ end
58
+
59
+ Hash[pairs]
60
+ end
61
+
62
+ def element_text(element_name)
63
+ doc.at(element_name)&.text
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VIISP
4
+ module Auth
5
+ module Requests
6
+ class Identity
7
+ include Soap
8
+ include Signature
9
+
10
+ NODE_ID = 'uniqueNodeId'
11
+
12
+ def initialize(ticket:, include_source_data: false)
13
+ @ticket = ticket
14
+ @include_source_data = include_source_data
15
+ end
16
+
17
+ def build
18
+ builder = Nokogiri::XML::Builder.new do |builder|
19
+ soap_envelope(builder) do
20
+ build_request(builder)
21
+ end
22
+ end
23
+
24
+ builder.doc
25
+ end
26
+
27
+ private
28
+
29
+ def build_request(builder)
30
+ builder[:authentication].authenticationDataRequest(id: NODE_ID) do
31
+ builder.pid(configuration.pid)
32
+ builder.ticket(@ticket)
33
+ builder.includeSourceData('true') if @include_source_data
34
+
35
+ build_signature(builder, NODE_ID)
36
+ end
37
+ end
38
+
39
+ def configuration
40
+ Auth.configuration
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VIISP
4
+ module Auth
5
+ module Requests
6
+ module Signature
7
+ def build_signature(builder, element_id)
8
+ builder[:ds].Signature do
9
+ builder.SignedInfo do
10
+ builder.CanonicalizationMethod(Algorithm: 'http://www.w3.org/2001/10/xml-exc-c14n#')
11
+ builder.SignatureMethod(Algorithm: 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256')
12
+ builder.Reference(URI: '#' + element_id) do
13
+ builder.Transforms do
14
+ builder.Transform(Algorithm: 'http://www.w3.org/2000/09/xmldsig#enveloped-signature')
15
+ end
16
+ builder.DigestMethod(Algorithm: 'http://www.w3.org/2001/04/xmlenc#sha256')
17
+ builder.DigestValue
18
+ end
19
+ end
20
+ builder.SignatureValue
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VIISP
4
+ module Auth
5
+ module Requests
6
+ module Soap
7
+ NAMESPACES = {
8
+ 'xmlns:soapenv' => 'http://schemas.xmlsoap.org/soap/envelope/',
9
+ 'xmlns:authentication' => 'http://www.epaslaugos.lt/services/authentication',
10
+ 'xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#',
11
+ }.freeze
12
+
13
+ def soap_envelope(builder)
14
+ builder[:soapenv].Envelope(NAMESPACES) do
15
+ builder.Body { yield }
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VIISP
4
+ module Auth
5
+ module Requests
6
+ class Ticket
7
+ include Soap
8
+ include Signature
9
+
10
+ NODE_ID = 'uniqueNodeId'
11
+
12
+ def initialize(providers: nil, attributes: nil, user_information: nil, postback_url: nil,
13
+ custom_data: '')
14
+ @providers = providers || configuration.providers
15
+ @attributes = attributes || configuration.attributes
16
+ @user_information = user_information || configuration.user_information
17
+ @postback_url = postback_url || configuration.postback_url
18
+ @custom_data = custom_data
19
+ end
20
+
21
+ def build
22
+ builder = Nokogiri::XML::Builder.new do |builder|
23
+ soap_envelope(builder) do
24
+ build_request(builder)
25
+ end
26
+ end
27
+
28
+ builder.doc
29
+ end
30
+
31
+ private
32
+
33
+ def build_request(builder)
34
+ builder[:authentication].authenticationRequest(id: NODE_ID) do
35
+ builder.pid(configuration.pid)
36
+
37
+ @providers.each do |provider|
38
+ builder.authenticationProvider(provider)
39
+ end
40
+
41
+ @attributes.each do |attribute|
42
+ builder.authenticationAttribute(attribute)
43
+ end
44
+
45
+ @user_information.each do |val|
46
+ builder.userInformation(val)
47
+ end
48
+
49
+ builder.postbackUrl(@postback_url)
50
+ builder.customData(@custom_data)
51
+
52
+ build_signature(builder, NODE_ID)
53
+ end
54
+ end
55
+
56
+ def configuration
57
+ Auth.configuration
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'xmldsig'
4
+
5
+ module VIISP
6
+ module Auth
7
+ module Signing
8
+ SCHEMAS_PATH = File.expand_path('../../../../schemas', __FILE__).freeze
9
+
10
+ module_function
11
+
12
+ def sign(doc, private_key = Auth.configuration.private_key)
13
+ signed_document = Xmldsig::SignedDocument.new(doc, id_attr: 'id')
14
+ signed_document.sign(private_key)
15
+ end
16
+
17
+ def validate!(doc, certificate = Auth.configuration.service_cert)
18
+ Dir.chdir(SCHEMAS_PATH) do
19
+ schema = IO.read('authentication.xsd')
20
+ signed_document = Xmldsig::SignedDocument.new(doc, id_attr: 'id')
21
+ signed_document.validate(certificate, schema) ||
22
+ raise(SignatureError, 'Unable to verify signature')
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VIISP
4
+ module Auth
5
+ VERSION = '0.1.0'
6
+ end
7
+ end
@@ -0,0 +1,154 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:dsig="http://www.w3.org/2000/09/xmldsig#" targetNamespace="http://www.epaslaugos.lt/services/authentication" elementFormDefault="qualified"
3
+ xmlns="http://www.epaslaugos.lt/services/authentication">
4
+
5
+ <xs:import namespace="http://www.w3.org/2000/09/xmldsig#" schemaLocation="xmldsig-core-schema.xsd" />
6
+ <xs:import namespace="http://www.w3.org/2001/10/xml-exc-c14n#" schemaLocation="exc-c14n.xsd"/>
7
+
8
+ <xs:element name="authenticationRequest">
9
+ <xs:complexType>
10
+ <xs:sequence>
11
+ <xs:element name="pid" type="xs:string" />
12
+ <xs:element name="serviceTarget" type="serviceTarget" minOccurs="0" />
13
+ <xs:element name="authenticationProvider" type="authenticationProvider" minOccurs="0" maxOccurs="unbounded" />
14
+ <xs:element name="authenticationAttribute" type="authenticationAttribute" minOccurs="0" maxOccurs="unbounded" />
15
+ <xs:element name="userInformation" type="userInformation" minOccurs="0" maxOccurs="unbounded" />
16
+ <xs:element name="postbackUrl" type="xs:anyURI" minOccurs="0" />
17
+ <xs:element name="customData" type="xs:string" minOccurs="0" />
18
+ <xs:element ref="dsig:Signature" />
19
+ </xs:sequence>
20
+ <xs:attribute name="id" type="xs:ID" use="optional"/>
21
+ </xs:complexType>
22
+ </xs:element>
23
+
24
+ <xs:element name="authenticationResponse">
25
+ <xs:complexType>
26
+ <xs:sequence>
27
+ <xs:element name="ticket" type="ticket" />
28
+ <xs:element ref="dsig:Signature" />
29
+ </xs:sequence>
30
+ <xs:attribute name="id" type="xs:ID" use="optional"/>
31
+ </xs:complexType>
32
+ </xs:element>
33
+
34
+ <xs:element name="authenticationDataRequest">
35
+ <xs:complexType>
36
+ <xs:sequence>
37
+ <xs:element name="pid" type="xs:string" />
38
+ <xs:element name="ticket" type="ticket" />
39
+ <xs:element name="includeSourceData" type="xs:boolean" minOccurs="0" />
40
+ <xs:element ref="dsig:Signature" />
41
+ </xs:sequence>
42
+ <xs:attribute name="id" type="xs:ID" use="optional"/>
43
+ </xs:complexType>
44
+ </xs:element>
45
+
46
+ <xs:element name="authenticationDataResponse">
47
+ <xs:complexType>
48
+ <xs:sequence>
49
+ <xs:element name="authenticationProvider" type="authenticationProvider" />
50
+ <xs:element name="authenticationAttribute" type="authenticationAttributePair" minOccurs="0" maxOccurs="unbounded" />
51
+ <xs:element name="userInformation" type="userInformationPair" minOccurs="0" maxOccurs="unbounded" />
52
+ <xs:element name="customData" type="xs:string" minOccurs="0" />
53
+ <xs:element name="sourceData" type="authenticationSourceData" minOccurs="0" />
54
+ <xs:element ref="dsig:Signature" />
55
+ </xs:sequence>
56
+ <xs:attribute name="id" type="xs:ID" use="optional"/>
57
+ </xs:complexType>
58
+ </xs:element>
59
+
60
+ <xs:element name="invalidSignatureException" />
61
+ <xs:element name="invalidXmlException" />
62
+
63
+ <xs:complexType name="authenticationAttributePair">
64
+ <xs:sequence>
65
+ <xs:element name="attribute" type="authenticationAttribute" />
66
+ <xs:element name="value" type="xs:string" />
67
+ </xs:sequence>
68
+ </xs:complexType>
69
+
70
+ <xs:complexType name="userInformationPair">
71
+ <xs:sequence>
72
+ <xs:element name="information" type="userInformation" />
73
+ <xs:element name="value">
74
+ <xs:complexType>
75
+ <xs:choice>
76
+ <xs:element name="stringValue" type="xs:string" />
77
+ <xs:element name="dateValue" type="xs:date" />
78
+ </xs:choice>
79
+ </xs:complexType>
80
+ </xs:element>
81
+ </xs:sequence>
82
+ </xs:complexType>
83
+
84
+ <xs:simpleType name="ticket">
85
+ <xs:restriction base="xs:string">
86
+ <xs:maxLength value="512" />
87
+ </xs:restriction>
88
+ </xs:simpleType>
89
+
90
+ <xs:simpleType name="serviceTarget">
91
+ <xs:restriction base="xs:string">
92
+ <xs:enumeration value="citizen" />
93
+ <xs:enumeration value="business" />
94
+ <xs:enumeration value="provider" />
95
+ </xs:restriction>
96
+ </xs:simpleType>
97
+
98
+ <xs:simpleType name="authenticationProvider">
99
+ <xs:restriction base="xs:string">
100
+ <xs:enumeration value="auth.login.pass" />
101
+ <xs:enumeration value="auth.lt.identity.card" />
102
+ <xs:enumeration value="auth.lt.government.employee.card" />
103
+ <xs:enumeration value="auth.lt.bank" />
104
+ <xs:enumeration value="auth.stork" />
105
+ <xs:enumeration value="auth.tsl.identity.card" />
106
+ <xs:enumeration value="auth.signatureProvider" />
107
+ </xs:restriction>
108
+ </xs:simpleType>
109
+
110
+ <xs:simpleType name="authenticationAttribute">
111
+ <xs:restriction base="xs:string">
112
+ <xs:enumeration value="lt-personal-code" />
113
+ <xs:enumeration value="lt-company-code" />
114
+ <xs:enumeration value="lt-government-employee-code" />
115
+ <xs:enumeration value="stork-eid" />
116
+ <xs:enumeration value="tsl-serial-number" />
117
+ <xs:enumeration value="login" />
118
+ </xs:restriction>
119
+ </xs:simpleType>
120
+
121
+ <xs:simpleType name="userInformation">
122
+ <xs:restriction base="xs:string">
123
+ <xs:enumeration value="id" />
124
+ <xs:enumeration value="firstName" />
125
+ <xs:enumeration value="lastName" />
126
+ <xs:enumeration value="address" />
127
+ <xs:enumeration value="email" />
128
+ <xs:enumeration value="phoneNumber" />
129
+ <xs:enumeration value="birthday" />
130
+ <xs:enumeration value="companyName" />
131
+ </xs:restriction>
132
+ </xs:simpleType>
133
+
134
+ <xs:complexType name="authenticationSourceData">
135
+ <xs:sequence>
136
+ <xs:element name="type" type="authenticationSourceType" />
137
+ <xs:element name="parameter" type="authenticationSourceParameter" maxOccurs="unbounded" />
138
+ </xs:sequence>
139
+ </xs:complexType>
140
+
141
+ <xs:simpleType name="authenticationSourceType">
142
+ <xs:restriction base="xs:string">
143
+ <xs:enumeration value="SAML" />
144
+ </xs:restriction>
145
+ </xs:simpleType>
146
+
147
+ <xs:complexType name="authenticationSourceParameter">
148
+ <xs:simpleContent>
149
+ <xs:extension base="xs:string">
150
+ <xs:attribute name="name" type="xs:string" />
151
+ </xs:extension>
152
+ </xs:simpleContent>
153
+ </xs:complexType>
154
+ </xs:schema>