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