openproject-token 1.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.
@@ -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: []