openproject-token 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1c30facc4e5d73dae3a1260b332edd3032fe1fd5
4
+ data.tar.gz: f4bff0f4de4653c09109a2429bf782ce8b394991
5
+ SHA512:
6
+ metadata.gz: d1b2556482fddcd2576379c2f9c0bf61338a6c373408a18bc3e4bb72300ed6b586fddbaec709cf7aa0f7e16758c3d630fc44f35baa9d1bd68cae29e08dc890ae
7
+ data.tar.gz: 5f9341a35e78b775a8114461626dd18040506736ccbcc938338c8ae24d92909de39a011aa8e630ce4fda220eaeee3bd1f106a484e513bc89958055fe91158d5f
@@ -0,0 +1,176 @@
1
+ require "openssl"
2
+ require "date"
3
+ require "json"
4
+ require "base64"
5
+
6
+ require 'active_model'
7
+
8
+ require "open_project/token/version"
9
+ require "open_project/token/extractor"
10
+ require "open_project/token/armor"
11
+
12
+ module OpenProject
13
+ class Token
14
+ class Error < StandardError; end
15
+ class ImportError < Error; end
16
+ class ParseError < Error; end
17
+ class ValidationError < Error; end
18
+
19
+ class << self
20
+ attr_reader :key, :extractor
21
+
22
+ def key=(key)
23
+ if key && !key.is_a?(OpenSSL::PKey::RSA)
24
+ raise ArgumentError, "Key is missing."
25
+ end
26
+
27
+ @key = key
28
+ @extractor = Extractor.new(self.key)
29
+ end
30
+
31
+ def import(data)
32
+ raise ImportError, "Missing key." if key.nil?
33
+ raise ImportError, "No token data." if data.nil?
34
+
35
+ data = Armor.decode(data)
36
+ json = extractor.read(data)
37
+ attributes = JSON.parse(json)
38
+
39
+ new(attributes)
40
+ rescue Extractor::Error
41
+ raise ImportError, "Token value could not be read."
42
+ rescue JSON::ParserError
43
+ raise ImportError, "Token value is invalid JSON."
44
+ rescue Armor::ParseError
45
+ raise ImportError, "Token value could not be parsed."
46
+ end
47
+ end
48
+
49
+ include ActiveModel::Validations
50
+
51
+ attr_reader :version
52
+ attr_accessor :subscriber, :mail
53
+ attr_accessor :starts_at, :issued_at, :expires_at
54
+ attr_accessor :notify_admins_at, :notify_users_at, :block_changes_at
55
+ attr_accessor :restrictions
56
+
57
+ validates_presence_of :subscriber
58
+ validates_presence_of :mail
59
+
60
+ validates_each(
61
+ :starts_at, :issued_at, :expires_at, :notify_admins_at, :notify_users_at, :block_changes_at,
62
+ allow_blank: true) do |record, attr, value|
63
+
64
+ record.errors.add attr, 'is not a date' if !value.is_a?(Date)
65
+ end
66
+
67
+ validates_each :restrictions, allow_nil: true do |record, attr, value|
68
+ record.errors.add attr, :invalid if !value.is_a?(Hash)
69
+ end
70
+
71
+ def initialize(attributes = {})
72
+ load_attributes(attributes)
73
+ end
74
+
75
+ def will_expire?
76
+ self.expires_at
77
+ end
78
+
79
+ def will_notify_admins?
80
+ self.notify_admins_at
81
+ end
82
+
83
+ def will_notify_users?
84
+ self.notify_users_at
85
+ end
86
+
87
+ def will_block_changes?
88
+ self.block_changes_at
89
+ end
90
+
91
+ def expired?
92
+ will_expire? && Date.today >= self.expires_at
93
+ end
94
+
95
+ def notify_admins?
96
+ will_notify_admins? && Date.today >= self.notify_admins_at
97
+ end
98
+
99
+ def notify_users?
100
+ will_notify_users? && Date.today >= self.notify_users_at
101
+ end
102
+
103
+ def block_changes?
104
+ will_block_changes? && Date.today >= self.block_changes_at
105
+ end
106
+
107
+ def restricted?(key = nil)
108
+ if key
109
+ restricted? && restrictions.has_key?(key)
110
+ else
111
+ restrictions && restrictions.length >= 1
112
+ end
113
+ end
114
+
115
+ def attributes
116
+ hash = {}
117
+
118
+ hash["version"] = self.version
119
+ hash["subscriber"] = self.subscriber
120
+ hash["mail"] = self.mail
121
+
122
+ hash["issued_at"] = self.issued_at
123
+ hash["starts_at"] = self.starts_at
124
+ hash["expires_at"] = self.expires_at if self.will_expire?
125
+
126
+ hash["notify_admins_at"] = self.notify_admins_at if self.will_notify_admins?
127
+ hash["notify_users_at"] = self.notify_users_at if self.will_notify_users?
128
+ hash["block_changes_at"] = self.block_changes_at if self.will_block_changes?
129
+
130
+ hash["restrictions"] = self.restrictions if self.restricted?
131
+
132
+ hash
133
+ end
134
+
135
+ def to_json
136
+ JSON.dump(self.attributes)
137
+ end
138
+
139
+ def from_json(json)
140
+ load_attributes(JSON.parse(json))
141
+ rescue => e
142
+ raise ParseError, "Failed to load from json: #{e}"
143
+ end
144
+
145
+ private
146
+
147
+ def load_attributes(attributes)
148
+ attributes = Hash[attributes.map { |k, v| [k.to_s, v] }]
149
+
150
+ version = attributes["version"] || 1
151
+ unless version && version == 1
152
+ raise ArgumentError, "Version is too new"
153
+ end
154
+
155
+ @version = version
156
+ @subscriber = attributes["subscriber"]
157
+ @mail = attributes["mail"]
158
+
159
+ %w(starts_at issued_at expires_at
160
+ notify_admins_at notify_users_at block_changes_at).each do |attr|
161
+ value = attributes[attr]
162
+ value = Date.parse(value) rescue nil if value.is_a?(String)
163
+
164
+ next unless value
165
+
166
+ send("#{attr}=", value)
167
+ end
168
+
169
+ restrictions = attributes["restrictions"]
170
+ if restrictions && restrictions.is_a?(Hash)
171
+ restrictions = Hash[restrictions.map { |k, v| [k.to_sym, v] }]
172
+ @restrictions = restrictions
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,38 @@
1
+ module OpenProject
2
+ class Token
3
+ module Armor
4
+ class ParseError < StandardError; end
5
+
6
+ MARKER = 'OPENPROJECT-EE TOKEN'
7
+
8
+ class << self
9
+ def header
10
+ "-----BEGIN #{MARKER}-----"
11
+ end
12
+
13
+ def footer
14
+ "-----END #{MARKER}-----"
15
+ end
16
+
17
+ def encode(data)
18
+ ''.tap do |s|
19
+ s << header << "\n"
20
+
21
+ s << data.strip << "\n"
22
+
23
+ s << footer
24
+ end
25
+ end
26
+
27
+ def decode(data)
28
+ match = data.match /#{header}\r?\n(.+?)\r?\n#{footer}/m
29
+ if match.nil?
30
+ raise ParseError, 'Failed to parse armored text.'
31
+ end
32
+
33
+ match[1]
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,68 @@
1
+ module OpenProject
2
+ class Token
3
+ class Extractor
4
+ class Error < StandardError; end
5
+ class KeyError < Error; end
6
+ class DecryptionError < Error; end
7
+
8
+ attr_accessor :key
9
+
10
+ def initialize(key)
11
+ @key = key
12
+ end
13
+
14
+ def read(data)
15
+ unless key.public?
16
+ raise KeyError, "Provided key is not a public key."
17
+ end
18
+
19
+ json_data = Base64.decode64(data.chomp)
20
+
21
+ begin
22
+ encryption_data = JSON.parse(json_data)
23
+ rescue JSON::ParserError
24
+ raise DecryptionError, "Encryption data is invalid JSON."
25
+ end
26
+
27
+ unless %w(data key iv).all? { |key| encryption_data[key] }
28
+ raise DecryptionError, "Required field missing from encryption data."
29
+ end
30
+
31
+ encrypted_data = Base64.decode64(encryption_data["data"])
32
+ encrypted_key = Base64.decode64(encryption_data["key"])
33
+ aes_iv = Base64.decode64(encryption_data["iv"])
34
+
35
+ begin
36
+ # Decrypt the AES key using asymmetric RSA encryption.
37
+ aes_key = self.key.public_decrypt(encrypted_key)
38
+ rescue OpenSSL::PKey::RSAError
39
+ raise DecryptionError, "AES encryption key could not be decrypted."
40
+ end
41
+
42
+ # Decrypt the data using symmetric AES encryption.
43
+ cipher = OpenSSL::Cipher::AES128.new(:CBC)
44
+ cipher.decrypt
45
+
46
+ begin
47
+ cipher.key = aes_key
48
+ rescue OpenSSL::Cipher::CipherError
49
+ raise DecryptionError, "AES encryption key is invalid."
50
+ end
51
+
52
+ begin
53
+ cipher.iv = aes_iv
54
+ rescue OpenSSL::Cipher::CipherError
55
+ raise DecryptionError, "AES IV is invalid."
56
+ end
57
+
58
+ begin
59
+ data = cipher.update(encrypted_data) + cipher.final
60
+ rescue OpenSSL::Cipher::CipherError
61
+ raise DecryptionError, "Data could not be decrypted."
62
+ end
63
+
64
+ data
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,5 @@
1
+ module OpenProject
2
+ class Token
3
+ VERSION = "1.0.0"
4
+ end
5
+ end
@@ -0,0 +1 @@
1
+ require 'open_project/token'
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: openproject-token
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - OpenProject GmbH
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-01-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activemodel
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.10'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.10'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.5'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.5'
55
+ description:
56
+ email: info@openproject.com
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - lib/open_project/token.rb
62
+ - lib/open_project/token/armor.rb
63
+ - lib/open_project/token/extractor.rb
64
+ - lib/open_project/token/version.rb
65
+ - lib/openproject-token.rb
66
+ homepage: https://www.openproject.org
67
+ licenses:
68
+ - GPLv3
69
+ metadata: {}
70
+ post_install_message:
71
+ rdoc_options: []
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ required_rubygems_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ requirements: []
85
+ rubyforge_project:
86
+ rubygems_version: 2.4.5.1
87
+ signing_key:
88
+ specification_version: 4
89
+ summary: OpenProject EE token reader
90
+ test_files: []