omniauth-latvija 1.1.1 → 2.0.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 +4 -4
- data/README.md +7 -4
- data/lib/omniauth-latvija/version.rb +1 -1
- data/lib/omniauth/strategies/latvija.rb +72 -67
- data/lib/omniauth/strategies/latvija/decryptor.rb +16 -0
- data/lib/omniauth/strategies/latvija/response.rb +39 -39
- data/lib/omniauth/strategies/latvija/signed_document.rb +62 -54
- metadata +35 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 875159da4f23d72890c3da6c24f1b174d197f6ec
|
4
|
+
data.tar.gz: af5a474b33c9165faf67a14a61b8785e0567a456
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 36fc98917bfe2547d020287fc90f6c4dcc986722c903be7acf49a3ffe56d7899d097661a0620352bb95646cfbc6db250cf911b89b2bbf603db1e3cc5125defed
|
7
|
+
data.tar.gz: 1f5947b59168bf23f22ab34c7b4b5ec83c2e01f4402a7035400bd885d00a3751d6eebffa741902f2c149a5d730b1f9ffb038f84015f40b6c514fe624290fe862
|
data/README.md
CHANGED
@@ -12,12 +12,14 @@ Provides the following authentication types:
|
|
12
12
|
* Online bank
|
13
13
|
* Citadele
|
14
14
|
* Norvik banka
|
15
|
+
* PrivatBank
|
16
|
+
* eID
|
15
17
|
* Lattelecom Mobile ID
|
16
18
|
|
17
19
|
## Installation
|
18
20
|
|
19
21
|
```ruby
|
20
|
-
gem 'omniauth-latvija',
|
22
|
+
gem 'omniauth-latvija', '~> 2.0'
|
21
23
|
```
|
22
24
|
|
23
25
|
## Usage
|
@@ -29,9 +31,10 @@ Here's a quick example, adding the middleware to a Rails app in `config/initiali
|
|
29
31
|
```ruby
|
30
32
|
Rails.application.config.middleware.use OmniAuth::Builder do
|
31
33
|
provider :latvija, {
|
32
|
-
:
|
33
|
-
:
|
34
|
-
:
|
34
|
+
endpoint: "https://epaktv.vraa.gov.lv/IVIS.LVP.STS/Default.aspx",
|
35
|
+
certificate: File.read("/path/to/cert.pem"),
|
36
|
+
private_key: File.read("/path/to/private_key.pem"), # mandatory, if the response is encrypted
|
37
|
+
realm: "urn:federation:example.com"
|
35
38
|
}
|
36
39
|
end
|
37
40
|
```
|
@@ -1,76 +1,81 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
1
|
+
require 'time'
|
2
|
+
require 'openssl'
|
3
|
+
require 'digest/sha1'
|
4
|
+
require 'xmlenc'
|
5
|
+
require 'nokogiri'
|
6
|
+
require 'omniauth/strategies/latvija/response'
|
7
|
+
require 'omniauth/strategies/latvija/decryptor'
|
8
|
+
require 'omniauth/strategies/latvija/signed_document'
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
+
module OmniAuth::Strategies
|
11
|
+
#
|
12
|
+
# Authenticate with Latvija.lv.
|
13
|
+
#
|
14
|
+
# @example Basic Rails Usage
|
15
|
+
#
|
16
|
+
# Add this to config/initializers/omniauth.rb
|
17
|
+
#
|
18
|
+
# Rails.application.config.middleware.use OmniAuth::Builder do
|
19
|
+
# provider :latvija, {
|
20
|
+
# endpoint: "https://epaktv.vraa.gov.lv/IVIS.LVP.STS/Default.aspx",
|
21
|
+
# certificate: File.read("/path/to/cert"),
|
22
|
+
# private: File.read("/path/to/private_key"),
|
23
|
+
# realm: "urn:federation:example.com"
|
24
|
+
# }
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
class Latvija
|
28
|
+
include OmniAuth::Strategy
|
29
|
+
class ValidationError < StandardError; end
|
10
30
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
# @example Basic Rails Usage
|
17
|
-
#
|
18
|
-
# Add this to config/initializers/omniauth.rb
|
19
|
-
#
|
20
|
-
# Rails.application.config.middleware.use OmniAuth::Builder do
|
21
|
-
# provider :latvija, {
|
22
|
-
# :endpoint => "https://epaktv.vraa.gov.lv/IVIS.LVP.STS/Default.aspx",
|
23
|
-
# :certificate => File.read("/path/to/cert"),
|
24
|
-
# :realm => "urn:federation:example.com"
|
25
|
-
# }
|
26
|
-
# end
|
27
|
-
#
|
28
|
-
class Latvija
|
29
|
-
include OmniAuth::Strategy
|
31
|
+
option :realm, nil
|
32
|
+
option :wfresh, false
|
33
|
+
option :endpoint, nil
|
34
|
+
option :certificate, nil
|
35
|
+
option :private_key, nil
|
30
36
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
redirect "#{options[:endpoint]}?#{query_string}"
|
45
|
-
end
|
37
|
+
def request_phase
|
38
|
+
params = {
|
39
|
+
wa: 'wsignin1.0',
|
40
|
+
wct: Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ'),
|
41
|
+
wtrealm: options[:realm],
|
42
|
+
wreply: callback_url,
|
43
|
+
wctx: callback_url,
|
44
|
+
wreq: '<trust:RequestSecurityToken xmlns:trust="http://docs.oasis-open.org/ws-sx/ws-trust/200512"><trust:Claims xmlns:i="http://schemas.xmlsoap.org/ws/2005/05/identity" Dialect="http://schemas.xmlsoap.org/ws/2005/05/identity"><i:ClaimType Uri="http://docs.oasis-open.org/wsfed/authorization/200706/claims/action" Optional="false" /></trust:Claims><trust:RequestType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</trust:RequestType></trust:RequestSecurityToken>'
|
45
|
+
}
|
46
|
+
params[:wfresh] = options[:wfresh] if options[:wfresh]
|
47
|
+
query_string = params.collect { |key, value| "#{key}=#{Rack::Utils.escape(value)}" }.join('&')
|
48
|
+
redirect "#{options[:endpoint]}?#{query_string}"
|
49
|
+
end
|
46
50
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
fail!(:invalid_response, e)
|
51
|
+
def callback_phase
|
52
|
+
if request.params['wresult']
|
53
|
+
@response = OmniAuth::Strategies::Latvija::Response.new(
|
54
|
+
request.params['wresult'],
|
55
|
+
certificate: options[:certificate],
|
56
|
+
private_key: options[:private_key]
|
57
|
+
)
|
58
|
+
@response.validate!
|
59
|
+
super
|
60
|
+
else
|
61
|
+
fail!(:invalid_response)
|
59
62
|
end
|
63
|
+
rescue Exception => e
|
64
|
+
fail!(:invalid_response, e)
|
65
|
+
end
|
60
66
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
end
|
67
|
+
def auth_hash
|
68
|
+
OmniAuth::Utils.deep_merge(super,
|
69
|
+
uid: "#{@response.attributes['givenname']} #{@response.attributes['surname']}, #{@response.attributes["privatepersonalidentifier"]}",
|
70
|
+
user_info: {
|
71
|
+
name: "#{@response.attributes['givenname']} #{@response.attributes['surname']}",
|
72
|
+
first_name: @response.attributes['givenname'],
|
73
|
+
last_name: @response.attributes['surname'],
|
74
|
+
private_personal_identifier: @response.attributes['privatepersonalidentifier']
|
75
|
+
},
|
76
|
+
authentication_method: @response.authentication_method,
|
77
|
+
extra: @response.attributes
|
78
|
+
)
|
74
79
|
end
|
75
80
|
end
|
76
81
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module OmniAuth::Strategies
|
2
|
+
class Latvija
|
3
|
+
class Decryptor
|
4
|
+
def initialize(response, key)
|
5
|
+
@response = response
|
6
|
+
@key = key
|
7
|
+
end
|
8
|
+
|
9
|
+
def decrypt
|
10
|
+
private_key = OpenSSL::PKey::RSA.new(@key)
|
11
|
+
encrypted_document = Xmlenc::EncryptedDocument.new(@response)
|
12
|
+
encrypted_document.decrypt(private_key)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -1,53 +1,53 @@
|
|
1
|
-
module OmniAuth
|
2
|
-
|
3
|
-
class
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
end
|
1
|
+
module OmniAuth::Strategies
|
2
|
+
class Latvija
|
3
|
+
class Response
|
4
|
+
ASSERTION = 'urn:oasis:names:tc:SAML:1.0:assertion'.freeze
|
5
|
+
|
6
|
+
attr_accessor :options, :response
|
7
|
+
|
8
|
+
def initialize(response, **options)
|
9
|
+
raise ArgumentError, 'Response cannot be nil' if response.nil?
|
10
|
+
@options = options
|
11
|
+
@response = response
|
12
|
+
@document = OmniAuth::Strategies::Latvija::SignedDocument.new(response, private_key: options[:private_key])
|
13
|
+
end
|
15
14
|
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
def validate!
|
16
|
+
@document.validate!(fingerprint)
|
17
|
+
end
|
19
18
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
end
|
24
|
-
end
|
19
|
+
def xml
|
20
|
+
@document.nokogiri_xml
|
21
|
+
end
|
25
22
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
23
|
+
def authentication_method
|
24
|
+
@authentication_method ||= begin
|
25
|
+
xml.xpath('//saml:AuthenticationStatement', saml: ASSERTION).attribute('AuthenticationMethod')
|
26
|
+
end
|
27
|
+
end
|
30
28
|
|
31
|
-
|
32
|
-
|
29
|
+
# A hash of all the attributes with the response.
|
30
|
+
# Assuming there is only one value for each key
|
31
|
+
def attributes
|
32
|
+
@attributes ||= begin
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
value = attr_element.elements.first.text
|
34
|
+
stmt_elements = xml.xpath('//a:Attribute', a: ASSERTION)
|
35
|
+
return {} if stmt_elements.nil?
|
37
36
|
|
38
|
-
|
39
|
-
|
37
|
+
stmt_elements.each_with_object({}) do |element, result|
|
38
|
+
name = element.attribute('AttributeName').value
|
39
|
+
value = element.text
|
40
40
|
|
41
|
-
result
|
41
|
+
result[name] = value
|
42
42
|
end
|
43
43
|
end
|
44
|
+
end
|
44
45
|
|
45
|
-
|
46
|
+
private
|
46
47
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
end
|
48
|
+
def fingerprint
|
49
|
+
cert = OpenSSL::X509::Certificate.new(options[:certificate])
|
50
|
+
Digest::SHA1.hexdigest(cert.to_der).upcase.scan(/../).join(':')
|
51
51
|
end
|
52
52
|
end
|
53
53
|
end
|
@@ -15,79 +15,87 @@
|
|
15
15
|
# If applicable, add the following below the CDDL Header,
|
16
16
|
# with the fields enclosed by brackets [] replaced by
|
17
17
|
# your own identifying information:
|
18
|
-
#
|
18
|
+
# 'Portions Copyrighted [year] [name of copyright owner]'
|
19
19
|
#
|
20
20
|
# $Id: xml_sec.rb,v 1.6 2007/10/24 00:28:41 todddd Exp $
|
21
21
|
#
|
22
22
|
# Copyright 2007 Sun Microsystems Inc. All Rights Reserved
|
23
23
|
# Portions Copyrighted 2007 Todd W Saxton.
|
24
24
|
|
25
|
-
module OmniAuth
|
26
|
-
|
27
|
-
class
|
28
|
-
|
29
|
-
|
25
|
+
module OmniAuth::Strategies
|
26
|
+
class Latvija
|
27
|
+
class SignedDocument
|
28
|
+
DSIG = 'http://www.w3.org/2000/09/xmldsig#'.freeze
|
29
|
+
XENC = 'http://www.w3.org/2001/04/xmlenc#'.freeze
|
30
|
+
CANON_MODE = Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
|
30
31
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
def validate!(idp_cert_fingerprint)
|
39
|
-
# get cert from response
|
40
|
-
base64_cert = self.elements["//ds:X509Certificate"].text
|
41
|
-
cert_text = Base64.decode64(base64_cert)
|
42
|
-
cert = OpenSSL::X509::Certificate.new(cert_text)
|
32
|
+
def initialize(response, **opts)
|
33
|
+
@response = Nokogiri::XML.parse(response, &:noblanks)
|
34
|
+
return unless encrypted?
|
35
|
+
decryptor = OmniAuth::Strategies::Latvija::Decryptor.new(response, opts[:private_key])
|
36
|
+
decrypted_response = decryptor.decrypt
|
37
|
+
@response = Nokogiri::XML.parse(decrypted_response, &:noblanks)
|
38
|
+
end
|
43
39
|
|
44
|
-
|
45
|
-
|
40
|
+
def validate!(idp_cert_fingerprint)
|
41
|
+
validate_fingerprint!(idp_cert_fingerprint)
|
42
|
+
sig_element = @response.xpath('//xmlns:Signature', xmlns: DSIG)
|
46
43
|
|
47
|
-
|
48
|
-
|
49
|
-
|
44
|
+
validate_digest!(sig_element)
|
45
|
+
validate_signature!(sig_element)
|
46
|
+
true
|
47
|
+
end
|
50
48
|
|
51
|
-
|
52
|
-
|
53
|
-
|
49
|
+
def nokogiri_xml
|
50
|
+
@response
|
51
|
+
end
|
54
52
|
|
55
|
-
|
56
|
-
REXML::XPath.each(sig_element, "//ds:Reference", {"ds"=>"http://www.w3.org/2000/09/xmldsig#"}) do |ref|
|
57
|
-
uri = ref.attributes.get_attribute("URI").value
|
58
|
-
hashed_element = REXML::XPath.first(self, "//[@AssertionID='#{uri[1,uri.size]}']")
|
59
|
-
canoner = XML::Util::XmlCanonicalizer.new(false, true)
|
60
|
-
canon_hashed_element = canoner.canonicalize(hashed_element)
|
61
|
-
hash = Base64.encode64(Digest::SHA1.digest(canon_hashed_element)).chomp
|
62
|
-
digest_value = REXML::XPath.first(ref, "//ds:DigestValue", { "ds" => DSIG }).text
|
53
|
+
private
|
63
54
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
end
|
55
|
+
def encrypted?
|
56
|
+
@response.xpath('//xenc:EncryptedData', 'xmlns:xenc' => XENC).any?
|
57
|
+
end
|
68
58
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
59
|
+
def certificate
|
60
|
+
@certificate ||= begin
|
61
|
+
base64_cert = @response.xpath('//xmlns:X509Certificate', xmlns: DSIG).text
|
62
|
+
cert_text = Base64.decode64(base64_cert)
|
63
|
+
OpenSSL::X509::Certificate.new(cert_text)
|
64
|
+
end
|
65
|
+
end
|
73
66
|
|
74
|
-
|
75
|
-
|
67
|
+
def validate_fingerprint!(idp_cert_fingerprint)
|
68
|
+
fingerprint = Digest::SHA1.hexdigest(certificate.to_der)
|
69
|
+
if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/, '').downcase
|
70
|
+
raise ValidationError, 'Fingerprint mismatch'
|
71
|
+
end
|
72
|
+
end
|
76
73
|
|
77
|
-
|
78
|
-
|
79
|
-
|
74
|
+
def validate_digest!(sig_element)
|
75
|
+
response_without_signature = @response.dup
|
76
|
+
response_without_signature.xpath('//xmlns:Signature', xmlns: DSIG).remove
|
80
77
|
|
81
|
-
|
82
|
-
|
83
|
-
|
78
|
+
sig_element.xpath('.//xmlns:Reference', xmlns: DSIG).each do |ref|
|
79
|
+
uri = ref.attribute('URI').value
|
80
|
+
hashed_element = response_without_signature.
|
81
|
+
at_xpath("//*[@AssertionID='#{uri[1, uri.size]}']").
|
82
|
+
canonicalize(CANON_MODE)
|
83
|
+
hash = Base64.encode64(Digest::SHA1.digest(hashed_element)).chomp
|
84
|
+
digest_value = ref.xpath('.//xmlns:DigestValue', xmlns: DSIG).text
|
84
85
|
|
85
|
-
|
86
|
+
raise ValidationError, 'Digest mismatch' if hash != digest_value
|
86
87
|
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def validate_signature!(sig_element)
|
91
|
+
signed_info_element = sig_element.
|
92
|
+
at_xpath('.//xmlns:SignedInfo', xmlns: DSIG).
|
93
|
+
canonicalize(CANON_MODE)
|
94
|
+
base64_signature = sig_element.xpath('.//xmlns:SignatureValue', xmlns: DSIG).text
|
95
|
+
signature = Base64.decode64(base64_signature)
|
87
96
|
|
88
|
-
|
89
|
-
|
90
|
-
self.signed_element_id = reference_element.attribute("URI").value unless reference_element.nil?
|
97
|
+
unless certificate.public_key.verify(OpenSSL::Digest::SHA1.new, signature, signed_info_element)
|
98
|
+
raise ValidationError, 'Key validation error'
|
91
99
|
end
|
92
100
|
end
|
93
101
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: omniauth-latvija
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Edgars Beigarts
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-10-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: omniauth
|
@@ -25,7 +25,7 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: xmlenc
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: nokogiri
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.5.1
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.5.1
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: rake
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,6 +80,20 @@ dependencies:
|
|
66
80
|
- - "~>"
|
67
81
|
- !ruby/object:Gem::Version
|
68
82
|
version: '2.10'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: byebug
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
69
97
|
- !ruby/object:Gem::Dependency
|
70
98
|
name: simplecov
|
71
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -106,6 +134,7 @@ files:
|
|
106
134
|
- lib/omniauth-latvija.rb
|
107
135
|
- lib/omniauth-latvija/version.rb
|
108
136
|
- lib/omniauth/strategies/latvija.rb
|
137
|
+
- lib/omniauth/strategies/latvija/decryptor.rb
|
109
138
|
- lib/omniauth/strategies/latvija/response.rb
|
110
139
|
- lib/omniauth/strategies/latvija/signed_document.rb
|
111
140
|
homepage:
|
@@ -117,9 +146,9 @@ require_paths:
|
|
117
146
|
- lib
|
118
147
|
required_ruby_version: !ruby/object:Gem::Requirement
|
119
148
|
requirements:
|
120
|
-
- - "
|
149
|
+
- - ">"
|
121
150
|
- !ruby/object:Gem::Version
|
122
|
-
version:
|
151
|
+
version: 2.1.0
|
123
152
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
124
153
|
requirements:
|
125
154
|
- - ">="
|
@@ -127,7 +156,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
127
156
|
version: '0'
|
128
157
|
requirements: []
|
129
158
|
rubyforge_project:
|
130
|
-
rubygems_version: 2.
|
159
|
+
rubygems_version: 2.6.11
|
131
160
|
signing_key:
|
132
161
|
specification_version: 4
|
133
162
|
summary: Latvija.lv authentication strategy for OmniAuth
|