senec 0.22.0 → 0.23.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 +4 -4
- data/.rspec_status +69 -66
- data/.rubocop.yml +3 -0
- data/.ruby-lsp/Gemfile.lock +72 -61
- data/.ruby-lsp/last_updated +1 -0
- data/.ruby-lsp/main_lockfile_hash +1 -1
- data/coverage/.last_run.json +1 -1
- data/coverage/.resultset.json +114 -50
- data/coverage/coverage.json +114 -50
- data/coverage/index.html +1457 -743
- data/lib/senec/cloud/connection.rb +48 -26
- data/lib/senec/version.rb +1 -1
- data/pkg/senec-0.22.0.gem +0 -0
- data/pkg/senec-0.22.1.gem +0 -0
- metadata +5 -3
- data/.ruby-lsp/needs_update +0 -0
|
@@ -40,8 +40,8 @@ module Senec
|
|
|
40
40
|
)
|
|
41
41
|
|
|
42
42
|
# Manual HTTP needed for Keycloak cross-domain form handling
|
|
43
|
-
|
|
44
|
-
redirect_url = submit_credentials(
|
|
43
|
+
form_type, form_url = fetch_login_form(auth_url)
|
|
44
|
+
redirect_url = submit_credentials(form_type, form_url)
|
|
45
45
|
authorization_code = extract_authorization_code(redirect_url)
|
|
46
46
|
|
|
47
47
|
self.oauth_token =
|
|
@@ -80,15 +80,19 @@ module Senec
|
|
|
80
80
|
|
|
81
81
|
attr_accessor :oauth_token
|
|
82
82
|
|
|
83
|
-
def
|
|
83
|
+
def fetch_login_form(auth_url)
|
|
84
84
|
response = http_request(:get, auth_url)
|
|
85
85
|
store_cookies(response) # Required for Keycloak CSRF protection
|
|
86
|
-
|
|
86
|
+
detect_login_form(response.body) || raise('Login form not found')
|
|
87
87
|
end
|
|
88
88
|
|
|
89
|
-
def submit_credentials(form_url)
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
def submit_credentials(form_type, form_url)
|
|
90
|
+
case form_type
|
|
91
|
+
when :username_and_password
|
|
92
|
+
response = http_request(:post, form_url, data: { username:, password: })
|
|
93
|
+
when :username_only
|
|
94
|
+
response = submit_username_then_password(form_url)
|
|
95
|
+
end
|
|
92
96
|
|
|
93
97
|
if response.status == 200
|
|
94
98
|
# Check if MFA is required
|
|
@@ -108,6 +112,20 @@ module Senec
|
|
|
108
112
|
handle_redirect_response(response)
|
|
109
113
|
end
|
|
110
114
|
|
|
115
|
+
def submit_username_then_password(form_url)
|
|
116
|
+
response = http_request(:post, form_url, data: { username: })
|
|
117
|
+
store_cookies(response)
|
|
118
|
+
|
|
119
|
+
raise 'Password form not found' unless response.status == 200
|
|
120
|
+
|
|
121
|
+
password_form_url = find_form_action_url(response.body) do |f|
|
|
122
|
+
f.match(/name=["']?password["']?/i)
|
|
123
|
+
end
|
|
124
|
+
raise 'Password form not found' unless password_form_url
|
|
125
|
+
|
|
126
|
+
http_request(:post, password_form_url, data: { username:, password: })
|
|
127
|
+
end
|
|
128
|
+
|
|
111
129
|
def submit_totp_form(form_url)
|
|
112
130
|
totp = build_totp_from_uri(totp_uri)
|
|
113
131
|
|
|
@@ -124,12 +142,21 @@ module Senec
|
|
|
124
142
|
params['code'] || raise('No authorization code found')
|
|
125
143
|
end
|
|
126
144
|
|
|
127
|
-
def
|
|
128
|
-
|
|
129
|
-
|
|
145
|
+
def detect_login_form(html)
|
|
146
|
+
# Check for combined form first (backward compatible with old SENEC flow)
|
|
147
|
+
url = find_form_action_url(html) do |form|
|
|
130
148
|
form.match(/name=["']?username["']?/i) &&
|
|
131
149
|
form.match(/name=["']?password["']?/i)
|
|
132
150
|
end
|
|
151
|
+
return [:username_and_password, url] if url
|
|
152
|
+
|
|
153
|
+
# Check for username-only form (new two-step SENEC flow)
|
|
154
|
+
url = find_form_action_url(html) do |form|
|
|
155
|
+
form.match(/name=["']?username["']?/i)
|
|
156
|
+
end
|
|
157
|
+
return [:username_only, url] if url
|
|
158
|
+
|
|
159
|
+
nil
|
|
133
160
|
end
|
|
134
161
|
|
|
135
162
|
def extract_totp_form_action_url(html)
|
|
@@ -169,48 +196,43 @@ module Senec
|
|
|
169
196
|
response.headers['location'] || raise('No redirect location')
|
|
170
197
|
end
|
|
171
198
|
|
|
172
|
-
def ensure_token_valid
|
|
199
|
+
def ensure_token_valid!
|
|
173
200
|
authenticate! unless authenticated?
|
|
174
|
-
return
|
|
201
|
+
return unless oauth_token.expired?
|
|
175
202
|
|
|
176
203
|
self.oauth_token = oauth_token.refresh!
|
|
177
|
-
true
|
|
178
204
|
rescue StandardError => e
|
|
179
|
-
#
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
# :nocov:
|
|
205
|
+
warn "Token refresh failed: #{e.message}, trying to re-authenticate..."
|
|
206
|
+
|
|
207
|
+
authenticate!
|
|
183
208
|
end
|
|
184
209
|
|
|
185
|
-
def get(url
|
|
186
|
-
|
|
210
|
+
def get(url)
|
|
211
|
+
ensure_token_valid!
|
|
187
212
|
|
|
188
213
|
response = oauth_token.get(url)
|
|
189
|
-
return default unless response.status == 200
|
|
190
|
-
|
|
191
214
|
JSON.parse(response.body)
|
|
192
215
|
rescue StandardError => e
|
|
193
216
|
# :nocov:
|
|
194
217
|
warn "API error: #{e.message}"
|
|
195
|
-
|
|
218
|
+
nil
|
|
196
219
|
# :nocov:
|
|
197
220
|
end
|
|
198
221
|
|
|
199
|
-
def post(url, data
|
|
200
|
-
|
|
222
|
+
def post(url, data)
|
|
223
|
+
ensure_token_valid!
|
|
201
224
|
|
|
202
225
|
response = oauth_token.post(
|
|
203
226
|
url,
|
|
204
227
|
body: data.to_json,
|
|
205
228
|
headers: { 'Content-Type' => 'application/json' },
|
|
206
229
|
)
|
|
207
|
-
return default unless response.status == 200
|
|
208
230
|
|
|
209
231
|
JSON.parse(response.body)
|
|
210
232
|
rescue StandardError => e
|
|
211
233
|
# :nocov:
|
|
212
234
|
warn "API error: #{e.message}"
|
|
213
|
-
|
|
235
|
+
nil
|
|
214
236
|
# :nocov:
|
|
215
237
|
end
|
|
216
238
|
|
data/lib/senec/version.rb
CHANGED
|
Binary file
|
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: senec
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.23.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Georg Ledermann
|
|
@@ -96,8 +96,8 @@ files:
|
|
|
96
96
|
- ".ruby-lsp/.gitignore"
|
|
97
97
|
- ".ruby-lsp/Gemfile"
|
|
98
98
|
- ".ruby-lsp/Gemfile.lock"
|
|
99
|
+
- ".ruby-lsp/last_updated"
|
|
99
100
|
- ".ruby-lsp/main_lockfile_hash"
|
|
100
|
-
- ".ruby-lsp/needs_update"
|
|
101
101
|
- ".vscode/settings.json"
|
|
102
102
|
- CODE_OF_CONDUCT.md
|
|
103
103
|
- LICENSE
|
|
@@ -164,6 +164,8 @@ files:
|
|
|
164
164
|
- pkg/senec-0.2.0.gem
|
|
165
165
|
- pkg/senec-0.20.0.gem
|
|
166
166
|
- pkg/senec-0.21.0.gem
|
|
167
|
+
- pkg/senec-0.22.0.gem
|
|
168
|
+
- pkg/senec-0.22.1.gem
|
|
167
169
|
- pkg/senec-0.3.0.gem
|
|
168
170
|
- pkg/senec-0.4.0.gem
|
|
169
171
|
- pkg/senec-0.5.0.gem
|
|
@@ -197,7 +199,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
197
199
|
- !ruby/object:Gem::Version
|
|
198
200
|
version: '0'
|
|
199
201
|
requirements: []
|
|
200
|
-
rubygems_version:
|
|
202
|
+
rubygems_version: 4.0.7
|
|
201
203
|
specification_version: 4
|
|
202
204
|
summary: Unofficial Ruby Client for SENEC Home
|
|
203
205
|
test_files: []
|
data/.ruby-lsp/needs_update
DELETED
|
File without changes
|