oktennyx 0.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.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/lib/collection.rb +54 -0
  3. data/lib/oktennyx.rb +269 -0
  4. metadata +44 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: bbe79893cadb011d993bc1a0ac9fb2a84de539cf8e33d8330be45ec61c89197b
4
+ data.tar.gz: 60ecbd755ae1fa5dd735c36f0429f4f22c2a58949b8e5e356e06b23fa4653fdb
5
+ SHA512:
6
+ metadata.gz: 6c7fe8f8aafe2164b133bbf1a6f932e8781d6e99c887aeb517073774447c2339a84662c656a6af71ff8c12371081e8d50370f98798af4b12bc407dd875fdc6b1
7
+ data.tar.gz: 14b2ef87378d6a27c747805fcf488db4eb2f02b5bb190760d58ba5efdad5449fe285a67d349148279612a778ff01da50dc90f783ea57f139d4f3cd136054e782
data/lib/collection.rb ADDED
@@ -0,0 +1,54 @@
1
+ require_relative 'oktennyx'
2
+
3
+ class Collection
4
+ attr_accessor :client, :response
5
+ def initialize(client, response)
6
+ @client = client
7
+ @response = response
8
+ @pagination_links = []
9
+ set_pagination
10
+ end
11
+
12
+ def each(&block)
13
+ @response[0].each(&block)
14
+ end
15
+
16
+ def set_pagination
17
+ # Checks for non-nil value in 1st indice. If pagination exists, this value will exist
18
+ if self.response[1]
19
+ @pagination_links = self.response[1].split(',')
20
+ end
21
+ end
22
+
23
+ def pages_remain
24
+ pages_remaining = nil
25
+
26
+ for link in @pagination_links
27
+ #latter conditional is in here for now because logs pagination is entering infinite loop and currently unsure why
28
+ if (link.include? 'rel="next') and (self.response[0] != [])
29
+ pages_remaining = true
30
+ end
31
+ end
32
+
33
+ return pages_remaining
34
+ end
35
+
36
+ def parse_pagination_link(links)
37
+ url = nil
38
+
39
+ for link in links
40
+ if link.include? 'rel="next'
41
+ url = (link.split(';')[0]).gsub(/[< >]/, '')
42
+ end
43
+ end
44
+ return url
45
+ end
46
+
47
+ def next
48
+ url = parse_pagination_link(@pagination_links)
49
+ self.response = self.client.http_req(URI(url), 'GET', {})
50
+ self.set_pagination
51
+ return self.response
52
+ end
53
+ end
54
+
data/lib/oktennyx.rb ADDED
@@ -0,0 +1,269 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+ require 'json'
4
+ require 'base64'
5
+ require 'OpenSSL'
6
+ require_relative 'collection'
7
+
8
+
9
+ class OktaClient
10
+ attr_accessor :org_url, :token, :client_id, :scopes, :private_key, :access_token, :pagination
11
+ def initialize(hash)
12
+ self.org_url = hash[:org_url]
13
+ self.token = hash[:token]
14
+ self.client_id = hash[:client_id]
15
+ self.scopes = hash[:scopes]
16
+ self.private_key = hash[:private_key]
17
+ self.access_token = nil
18
+ check_authz_type
19
+ end
20
+
21
+ def http_req(url, http_method, body)
22
+ https = Net::HTTP.new(url.host, url.port);
23
+ https.use_ssl = true
24
+
25
+ if http_method == 'GET'
26
+ request = Net::HTTP::Get.new(url)
27
+ elsif http_method == 'PUT'
28
+ request = Net::HTTP::Put.new(url)
29
+ elsif http_method == 'POST'
30
+ request = Net::HTTP::Post.new(url)
31
+ elsif http_method == 'DELETE'
32
+ request = Net::HTTP::Delete.new(url)
33
+ else
34
+ return 405
35
+ end
36
+
37
+ request['Accept'] = 'application/json'
38
+ request['Content-Type'] = 'application/json'
39
+
40
+ if self.access_token
41
+ request['Authorization'] = "Bearer #{self.access_token}"
42
+ else
43
+ request['Authorization'] = "SSWS #{self.token}"
44
+ end
45
+
46
+ if not body.empty?
47
+ request.body = body
48
+ end
49
+
50
+ response = https.request(request)
51
+
52
+ if response.code == '204'
53
+ return response.code
54
+ else
55
+ return JSON.parse(response.read_body), response['Link']
56
+ end
57
+ end
58
+
59
+ def to_hex(int)
60
+ int < 16 ? '0' + int.to_s(16) : int.to_s(16)
61
+ end
62
+
63
+ def base64_to_long(data)
64
+ decoded_with_padding = Base64.urlsafe_decode64(data) + Base64.decode64('==')
65
+ decoded_with_padding.to_s.unpack('C*').map do |byte|
66
+ self.to_hex(byte)
67
+ end.join.to_i(16)
68
+ end
69
+
70
+ def get_access_token(private_key, client_id, scopes)
71
+ scopes_string = ''
72
+ scopes.each {|scope| scopes_string += "#{scope} "}
73
+
74
+ jwks = private_key
75
+
76
+ jwtheader = {
77
+ 'alg': 'RS256'
78
+ }
79
+
80
+ jwtpayload = {
81
+ aud: "#{self.org_url}/oauth2/v1/token",
82
+ exp: (Time.now + 1*60*60).utc.strftime('%s'),
83
+ iss: client_id,
84
+ sub: client_id
85
+ }
86
+
87
+ jwtheaderJSON = jwtheader.to_json
88
+ jwtheaderUTF = jwtheaderJSON.encode('UTF-8')
89
+ tokenheader = Base64.urlsafe_encode64(jwtheaderUTF)
90
+
91
+
92
+ jwtpayloadJSON = jwtpayload.to_json
93
+ jwtpayloadUTF = jwtpayloadJSON.encode('UTF-8')
94
+ tokenpayload = Base64.urlsafe_encode64(jwtpayloadUTF)
95
+
96
+
97
+ signeddata = tokenheader + "." + tokenpayload
98
+
99
+ signature = ''
100
+
101
+ if private_key.class == Hash
102
+ key = OpenSSL::PKey::RSA.new 2048
103
+ exponent = private_key[:keys][0][:e]
104
+ modulus = private_key[:keys][0][:n]
105
+ key.set_key(self.base64_to_long(modulus), self.base64_to_long(exponent), self.base64_to_long(jwks[:keys][0][:d]))
106
+ signature = Base64.urlsafe_encode64(key.sign(OpenSSL::Digest::SHA256.new, signeddata))
107
+ elsif private_key.class == String
108
+ priv = private_key
109
+ key = OpenSSL::PKey::RSA.new(priv)
110
+ signature = Base64.urlsafe_encode64(key.sign(OpenSSL::Digest::SHA256.new, signeddata))
111
+ end
112
+
113
+ client_secret_jwt = signeddata + '.' + signature
114
+
115
+ url = URI("#{self.org_url}/oauth2/v1/token")
116
+ https = Net::HTTP.new(url.host, url.port);
117
+ https.use_ssl = true
118
+ request = Net::HTTP::Post.new(url)
119
+ request['Accept'] = 'application/json'
120
+ request['Content-Type'] = 'application/x-www-form-urlencoded'
121
+ request.body = "grant_type=client_credentials&scope=#{scopes_string}&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=#{client_secret_jwt}"
122
+ response = https.request(request)
123
+ self.access_token = JSON.parse(response.read_body)['access_token']
124
+ end
125
+
126
+ def check_authz_type
127
+ if self.private_key
128
+ self.get_access_token(self.private_key, self.client_id, self.scopes)
129
+ end
130
+ end
131
+
132
+ def handle_params(params)
133
+ query_params = ''
134
+
135
+ if not params.empty?
136
+ query_params += '?'
137
+ for param_key, param_value in params
138
+ query_params += "#{param_key}=#{param_value}&"
139
+ end
140
+ end
141
+
142
+ return query_params
143
+ end
144
+
145
+ #---user methods
146
+
147
+ def get_users(**params)
148
+ query_params = self.handle_params(params)
149
+ url = URI("#{self.org_url}/api/v1/users/#{query_params}")
150
+ users_collection = Collection.new(self, self.http_req(url, 'GET', {}))
151
+ return users_collection
152
+ end
153
+
154
+ def get_user(user_id)
155
+ url = URI("#{self.org_url}/api/v1/users/#{user_id}")
156
+ self.http_req(url, 'GET', {})[0]
157
+ end
158
+
159
+ def create_user(profile)
160
+ url = URI("#{self.org_url}/api/v1/users")
161
+ self.http_req(url, 'POST', profile.to_json)
162
+ end
163
+
164
+ def update_user(user_id, profile)
165
+ url = URI("#{self.org_url}/api/v1/users/#{user_id}")
166
+ new_profile = {profile: profile}
167
+ self.http_req(url, 'PUT', new_profile.to_json)
168
+ end
169
+
170
+ def deactivate_user(user_id)
171
+ url = URI("#{self.org_url}/api/v1/users/#{user_id}/lifecycle/deactivate")
172
+ self.http_req(url, 'POST', {})
173
+ end
174
+
175
+ def delete_user(user_id)
176
+ url = URI("#{self.org_url}/api/v1/users/#{user_id}")
177
+ self.http_req(url, 'DELETE', {})
178
+ end
179
+
180
+ def get_user_groups(user_id)
181
+ url = URI("#{self.org_url}/api/v1/users/#{user_id}/groups")
182
+ self.http_req(url, 'GET', {})
183
+ end
184
+
185
+ def get_user_factors(user_id)
186
+ url = URI("#{self.org_url}/api/v1/users/#{user_id}/factors")
187
+ self.http_req(url, 'GET', {})
188
+ end
189
+
190
+ def enroll_factor(user_id, factor_profile)
191
+ url = URI("#{self.org_url}/api/v1/users/#{user_id}/factors")
192
+ self.http_req(url, 'POST', factor_profile.to_json)
193
+ end
194
+
195
+ def activate_factor(user_id, factor_id, activation_profile)
196
+ url = URI("#{self.org_url}/api/v1/users/#{user_id}/factors/#{factor_id}/lifecycle/activate")
197
+ self.http_req(url, 'POST', activation_profile.to_json)
198
+ end
199
+
200
+ def verify_factor(user_id, factor_id, verify_profile)
201
+ url = URI("#{self.org_url}/api/v1/users/#{user_id}/factors/#{factor_id}/verify")
202
+ self.http_req(url, 'POST', verify_profile.to_json)
203
+ end
204
+
205
+ #---group methods
206
+
207
+ def get_groups(**params)
208
+ query_params = self.handle_params(params)
209
+ url = URI("#{self.org_url}/api/v1/groups/#{query_params}")
210
+ groups_collection = Collection.new(self, self.http_req(url, 'GET', {}))
211
+ return groups_collection
212
+ end
213
+
214
+ def get_group(group_id)
215
+ url = URI("#{self.org_url}/api/v1/groups/#{group_id}")
216
+ self.http_req(url, 'GET', {})
217
+ end
218
+
219
+ def create_group(profile)
220
+ url = URI("#{self.org_url}/api/v1/groups")
221
+ self.http_req(url, 'POST', profile.to_json)
222
+ end
223
+
224
+ def add_user_to_group(user_id, group_id)
225
+ url = URI("#{self.org_url}/api/v1/groups/#{group_id}/users/#{user_id}")
226
+ self.http_req(url, 'PUT', {})
227
+ end
228
+
229
+ def remove_user_from_group(user_id, group_id)
230
+ url = URI("#{self.org_url}/api/v1/groups/#{group_id}/users/#{user_id}")
231
+ self.http_req(url, 'DELETE', {})
232
+ end
233
+
234
+ #---app methods
235
+
236
+ def get_applications(**params)
237
+ query_params = self.handle_params(params)
238
+ url = URI("#{self.org_url}/api/v1/apps/#{query_params}")
239
+ apps_collection = Collection.new(self, self.http_req(url, 'GET', {}))
240
+ return apps_collection
241
+ end
242
+
243
+ def get_application(app_id)
244
+ url = URI("#{self.org_url}/api/v1/apps/#{app_id}")
245
+ self.http_req(url, 'GET', {})
246
+ end
247
+
248
+ def create_application(app_profile)
249
+ url = URI("#{self.org_url}/api/v1/apps")
250
+ self.http_req(url, 'POST', app_profile.to_json)
251
+ end
252
+
253
+ def assign_user_to_app(user_id, user_profile, app_id)
254
+ url = URI("#{self.org_url}/api/v1/apps/#{app_id}/users")
255
+ user = {
256
+ id: user_id,
257
+ }
258
+ self.http_req(url, 'POST', user.to_json)
259
+ end
260
+
261
+ #---logs methods
262
+
263
+ def get_logs(**params)
264
+ query_params = self.handle_params(params)
265
+ url = URI("#{self.org_url}/api/v1/logs/#{query_params}")
266
+ logs_collection = Collection.new(self, self.http_req(url, 'GET', {}))
267
+ return logs_collection
268
+ end
269
+ end
metadata ADDED
@@ -0,0 +1,44 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: oktennyx
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Cale Switzer
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-02-03 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Unofficial Ruby Management SDK to interact with Okta orgs.
14
+ email: cale.switzer@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - lib/collection.rb
20
+ - lib/oktennyx.rb
21
+ homepage: https://rubygems.org/gems/oktennyx
22
+ licenses:
23
+ - MIT
24
+ metadata: {}
25
+ post_install_message:
26
+ rdoc_options: []
27
+ require_paths:
28
+ - lib
29
+ required_ruby_version: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ required_rubygems_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ requirements: []
40
+ rubygems_version: 3.0.3.1
41
+ signing_key:
42
+ specification_version: 4
43
+ summary: Ruby Management SDK for Okta
44
+ test_files: []