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.
- checksums.yaml +7 -0
- data/lib/collection.rb +54 -0
- data/lib/oktennyx.rb +269 -0
- 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: []
|