senec 0.22.1 → 0.24.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 +71 -62
- data/.ruby-lsp/Gemfile +1 -1
- data/.ruby-lsp/Gemfile.lock +80 -77
- data/.ruby-lsp/freshness_hash +1 -0
- data/.ruby-lsp/last_updated +1 -1
- data/.ruby-lsp/main_lockfile_hash +1 -1
- data/coverage/.last_run.json +1 -1
- data/coverage/.resultset.json +1 -1542
- data/coverage/coverage.json +870 -942
- data/coverage/index.html +68 -16793
- data/lib/senec/cloud/connection.rb +65 -22
- data/lib/senec/version.rb +1 -1
- data/pkg/senec-0.22.1.gem +0 -0
- data/pkg/senec-0.23.0.gem +0 -0
- metadata +6 -3
- /data/{.ruby-lsp/needs_update → coverage/.report_stamp} +0 -0
|
@@ -8,7 +8,7 @@ module Senec
|
|
|
8
8
|
|
|
9
9
|
CLIENT_ID = 'endcustomer-app-frontend'.freeze
|
|
10
10
|
REDIRECT_URI = 'senec-app-auth://keycloak.prod'.freeze
|
|
11
|
-
SCOPE = 'roles meinsenec'.freeze
|
|
11
|
+
SCOPE = 'roles profile meinsenec'.freeze
|
|
12
12
|
|
|
13
13
|
SYSTEMS_HOST = 'https://senec-app-systems-proxy.prod.senec.dev'.freeze
|
|
14
14
|
MEASUREMENTS_HOST = 'https://senec-app-measurements-proxy.prod.senec.dev'.freeze
|
|
@@ -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 =
|
|
@@ -57,38 +57,58 @@ module Senec
|
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
def systems
|
|
60
|
-
get "#{SYSTEMS_HOST}/v1
|
|
60
|
+
get "#{SYSTEMS_HOST}/systems/api/v1"
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
def system_details(system_id)
|
|
64
|
-
get "#{SYSTEMS_HOST}/systems/#{system_id}/details"
|
|
64
|
+
get "#{SYSTEMS_HOST}/systems/api/v1/#{system_id}/details"
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
def dashboard(system_id)
|
|
68
|
-
get "#{MEASUREMENTS_HOST}/v1/systems/#{system_id}/dashboard"
|
|
68
|
+
get "#{MEASUREMENTS_HOST}/measurements/api/v1/systems/#{system_id}/dashboard"
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
def wallbox(system_id, wallbox_id)
|
|
72
|
-
get "#{WALLBOX_HOST}/v1/systems/#{system_id}/wallboxes/#{wallbox_id}"
|
|
72
|
+
get "#{WALLBOX_HOST}/wallbox/api/v1/systems/#{system_id}/wallboxes/#{wallbox_id}"
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
def wallbox_search(system_id)
|
|
76
|
-
post "#{WALLBOX_HOST}/v1/systems/wallboxes/search", { systemIds: [system_id] }
|
|
76
|
+
post "#{WALLBOX_HOST}/wallbox/api/v1/systems/wallboxes/search", { systemIds: [system_id] }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def measurements(system_id, resolution:, from:, to:)
|
|
80
|
+
params = URI.encode_www_form(
|
|
81
|
+
resolution:,
|
|
82
|
+
from: from.strftime('%Y-%m-%dT%H:%M:%SZ'),
|
|
83
|
+
to: to.strftime('%Y-%m-%dT%H:%M:%SZ'),
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
get "#{MEASUREMENTS_HOST}/measurements/api/v1/systems/#{system_id}/measurements?#{params}"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def data_availability(system_id, timezone:)
|
|
90
|
+
params = URI.encode_www_form(timezone:)
|
|
91
|
+
|
|
92
|
+
get "#{MEASUREMENTS_HOST}/measurements/api/v1/systems/#{system_id}/data-availability/timespan?#{params}"
|
|
77
93
|
end
|
|
78
94
|
|
|
79
95
|
private
|
|
80
96
|
|
|
81
97
|
attr_accessor :oauth_token
|
|
82
98
|
|
|
83
|
-
def
|
|
99
|
+
def fetch_login_form(auth_url)
|
|
84
100
|
response = http_request(:get, auth_url)
|
|
85
101
|
store_cookies(response) # Required for Keycloak CSRF protection
|
|
86
|
-
|
|
102
|
+
detect_login_form(response.body) || raise('Login form not found')
|
|
87
103
|
end
|
|
88
104
|
|
|
89
|
-
def submit_credentials(form_url)
|
|
90
|
-
|
|
91
|
-
|
|
105
|
+
def submit_credentials(form_type, form_url)
|
|
106
|
+
case form_type
|
|
107
|
+
when :username_and_password
|
|
108
|
+
response = http_request(:post, form_url, data: { username:, password: })
|
|
109
|
+
when :username_only
|
|
110
|
+
response = submit_username_then_password(form_url)
|
|
111
|
+
end
|
|
92
112
|
|
|
93
113
|
if response.status == 200
|
|
94
114
|
# Check if MFA is required
|
|
@@ -108,6 +128,20 @@ module Senec
|
|
|
108
128
|
handle_redirect_response(response)
|
|
109
129
|
end
|
|
110
130
|
|
|
131
|
+
def submit_username_then_password(form_url)
|
|
132
|
+
response = http_request(:post, form_url, data: { username: })
|
|
133
|
+
store_cookies(response)
|
|
134
|
+
|
|
135
|
+
raise 'Password form not found' unless response.status == 200
|
|
136
|
+
|
|
137
|
+
password_form_url = find_form_action_url(response.body) do |f|
|
|
138
|
+
f.match(/name=["']?password["']?/i)
|
|
139
|
+
end
|
|
140
|
+
raise 'Password form not found' unless password_form_url
|
|
141
|
+
|
|
142
|
+
http_request(:post, password_form_url, data: { username:, password: })
|
|
143
|
+
end
|
|
144
|
+
|
|
111
145
|
def submit_totp_form(form_url)
|
|
112
146
|
totp = build_totp_from_uri(totp_uri)
|
|
113
147
|
|
|
@@ -124,12 +158,21 @@ module Senec
|
|
|
124
158
|
params['code'] || raise('No authorization code found')
|
|
125
159
|
end
|
|
126
160
|
|
|
127
|
-
def
|
|
128
|
-
|
|
129
|
-
|
|
161
|
+
def detect_login_form(html)
|
|
162
|
+
# Check for combined form first (backward compatible with old SENEC flow)
|
|
163
|
+
url = find_form_action_url(html) do |form|
|
|
130
164
|
form.match(/name=["']?username["']?/i) &&
|
|
131
165
|
form.match(/name=["']?password["']?/i)
|
|
132
166
|
end
|
|
167
|
+
return [:username_and_password, url] if url
|
|
168
|
+
|
|
169
|
+
# Check for username-only form (new two-step SENEC flow)
|
|
170
|
+
url = find_form_action_url(html) do |form|
|
|
171
|
+
form.match(/name=["']?username["']?/i)
|
|
172
|
+
end
|
|
173
|
+
return [:username_only, url] if url
|
|
174
|
+
|
|
175
|
+
nil
|
|
133
176
|
end
|
|
134
177
|
|
|
135
178
|
def extract_totp_form_action_url(html)
|
|
@@ -186,10 +229,10 @@ module Senec
|
|
|
186
229
|
response = oauth_token.get(url)
|
|
187
230
|
JSON.parse(response.body)
|
|
188
231
|
rescue StandardError => e
|
|
189
|
-
# :
|
|
232
|
+
# simplecov:disable
|
|
190
233
|
warn "API error: #{e.message}"
|
|
191
234
|
nil
|
|
192
|
-
# :
|
|
235
|
+
# simplecov:enable
|
|
193
236
|
end
|
|
194
237
|
|
|
195
238
|
def post(url, data)
|
|
@@ -203,10 +246,10 @@ module Senec
|
|
|
203
246
|
|
|
204
247
|
JSON.parse(response.body)
|
|
205
248
|
rescue StandardError => e
|
|
206
|
-
# :
|
|
249
|
+
# simplecov:disable
|
|
207
250
|
warn "API error: #{e.message}"
|
|
208
251
|
nil
|
|
209
|
-
# :
|
|
252
|
+
# simplecov:enable
|
|
210
253
|
end
|
|
211
254
|
|
|
212
255
|
def http_request(method, url, data: nil)
|
|
@@ -234,9 +277,9 @@ module Senec
|
|
|
234
277
|
def openid_config
|
|
235
278
|
@openid_config ||= JSON.parse(http_request(:get, CONFIG_URL).body)
|
|
236
279
|
rescue StandardError => e
|
|
237
|
-
# :
|
|
280
|
+
# simplecov:disable
|
|
238
281
|
raise "Failed to load OpenID configuration: #{e.message}"
|
|
239
|
-
# :
|
|
282
|
+
# simplecov:enable
|
|
240
283
|
end
|
|
241
284
|
|
|
242
285
|
def cookies
|
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.24.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Georg Ledermann
|
|
@@ -96,15 +96,16 @@ files:
|
|
|
96
96
|
- ".ruby-lsp/.gitignore"
|
|
97
97
|
- ".ruby-lsp/Gemfile"
|
|
98
98
|
- ".ruby-lsp/Gemfile.lock"
|
|
99
|
+
- ".ruby-lsp/freshness_hash"
|
|
99
100
|
- ".ruby-lsp/last_updated"
|
|
100
101
|
- ".ruby-lsp/main_lockfile_hash"
|
|
101
|
-
- ".ruby-lsp/needs_update"
|
|
102
102
|
- ".vscode/settings.json"
|
|
103
103
|
- CODE_OF_CONDUCT.md
|
|
104
104
|
- LICENSE
|
|
105
105
|
- README.md
|
|
106
106
|
- Rakefile
|
|
107
107
|
- coverage/.last_run.json
|
|
108
|
+
- coverage/.report_stamp
|
|
108
109
|
- coverage/.resultset.json
|
|
109
110
|
- coverage/.resultset.json.lock
|
|
110
111
|
- coverage/assets/0.13.2/DataTables-1.10.20/images/sort_asc.png
|
|
@@ -166,6 +167,8 @@ files:
|
|
|
166
167
|
- pkg/senec-0.20.0.gem
|
|
167
168
|
- pkg/senec-0.21.0.gem
|
|
168
169
|
- pkg/senec-0.22.0.gem
|
|
170
|
+
- pkg/senec-0.22.1.gem
|
|
171
|
+
- pkg/senec-0.23.0.gem
|
|
169
172
|
- pkg/senec-0.3.0.gem
|
|
170
173
|
- pkg/senec-0.4.0.gem
|
|
171
174
|
- pkg/senec-0.5.0.gem
|
|
@@ -199,7 +202,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
199
202
|
- !ruby/object:Gem::Version
|
|
200
203
|
version: '0'
|
|
201
204
|
requirements: []
|
|
202
|
-
rubygems_version:
|
|
205
|
+
rubygems_version: 4.0.19
|
|
203
206
|
specification_version: 4
|
|
204
207
|
summary: Unofficial Ruby Client for SENEC Home
|
|
205
208
|
test_files: []
|
|
File without changes
|