viisp-auth 0.1.0

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