bandshell 0.0.20 → 0.0.21
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/bin/bandshell-timer.rb +34 -0
- data/bin/bandshelld +14 -2
- data/bin/concerto_netsetup +0 -1
- data/lib/bandshell/application/app.rb +440 -312
- data/lib/bandshell/application/config.ru +4 -1
- data/lib/bandshell/application/public/authenticate.js +134 -0
- data/lib/bandshell/application/public/network.js +15 -15
- data/lib/bandshell/application/public/problem.js +2 -2
- data/lib/bandshell/application/views/authenticate.haml +15 -0
- data/lib/bandshell/application/views/main.haml +1 -1
- data/lib/bandshell/application/views/player_status.haml +10 -7
- data/lib/bandshell/application/views/setup.haml +7 -2
- data/lib/bandshell/config_store.rb +36 -36
- data/lib/bandshell/hardware_api.rb +180 -0
- data/lib/bandshell/live_image.rb +26 -26
- data/lib/bandshell/netconfig.rb +459 -458
- metadata +35 -48
- data/bin/concerto_configserver +0 -10
@@ -0,0 +1,134 @@
|
|
1
|
+
/* Concerto Authentication Javascript
|
2
|
+
* Handles authenticating the player browser to Concerto and sending it
|
3
|
+
* to the frontend.
|
4
|
+
*
|
5
|
+
* Basic workflow:
|
6
|
+
* - If the screen does not yet have a valid token:
|
7
|
+
* + displays the temporary token
|
8
|
+
* + polls the local server until the local server has a permanent token
|
9
|
+
* + continues to the next section:
|
10
|
+
* - If the screen has a valid token:
|
11
|
+
* + requests the relevant data from the local server
|
12
|
+
* + performs a one-time authenticated request to concerto to get
|
13
|
+
* an authentication cookie for the browser
|
14
|
+
* + redirects to the frontend
|
15
|
+
*/
|
16
|
+
|
17
|
+
var checkIntHandle; // Handle for the polling event
|
18
|
+
var verbose = false;
|
19
|
+
|
20
|
+
window.arrify = function(args) {
|
21
|
+
return (args.length == 1) ? args[0] : Array.prototype.slice.call(args);
|
22
|
+
}
|
23
|
+
|
24
|
+
// usage: log('inside coolFunc',this,arguments);
|
25
|
+
// http://paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/
|
26
|
+
window.log = function(){
|
27
|
+
if(this.console) console.log( arrify(arguments) );
|
28
|
+
};
|
29
|
+
window.error = function(){
|
30
|
+
// we'll use warn for "errors" so we can clarify between
|
31
|
+
// logistical problems and actual JS errors.
|
32
|
+
if(this.console) console.warn( arrify(arguments) );
|
33
|
+
};
|
34
|
+
window.debug = function(){
|
35
|
+
if(this.console && verbose) console.debug( arrify(arguments));
|
36
|
+
};
|
37
|
+
|
38
|
+
function update_status(statstring) {
|
39
|
+
var stat=document.getElementById("status");
|
40
|
+
stat.innerHTML=statstring;
|
41
|
+
}
|
42
|
+
|
43
|
+
function check_token() {
|
44
|
+
// make a request to localhost to see if we are ready to redirect
|
45
|
+
var req = new XMLHttpRequest();
|
46
|
+
req.addEventListener("load", function() {
|
47
|
+
if (req.status != 200) {
|
48
|
+
update_status("Error! Something went wrong with Bandshell.");
|
49
|
+
error("Recieved an unexpected response from Bandshell, "+
|
50
|
+
"status = "+req.status+" ("+req.statusText+")");
|
51
|
+
} else {
|
52
|
+
update_status("");
|
53
|
+
try {
|
54
|
+
data = JSON.parse(this.responseText);
|
55
|
+
} catch (e) {
|
56
|
+
update_status("Error: Bad data from Bandshell.");
|
57
|
+
error("There was a problem with the JSON response from Bandshell:\n"+
|
58
|
+
" "+e.toString()+"\n"+
|
59
|
+
"The text in question was:\n"+
|
60
|
+
" "+this.responseText);
|
61
|
+
return;
|
62
|
+
}
|
63
|
+
if (data.accepted==1) {
|
64
|
+
update_status("Success!");
|
65
|
+
log("Temporary token accepted by the server.");
|
66
|
+
redirect_with_auth(data.url, data.user, data.pass);
|
67
|
+
} else {
|
68
|
+
update_status("");
|
69
|
+
debug("Token request declined.");
|
70
|
+
}
|
71
|
+
}
|
72
|
+
});
|
73
|
+
req.addEventListener("error", function() {
|
74
|
+
update_status("Error! Bandshell is not responding.");
|
75
|
+
error("Request to Bandshell failed. "+
|
76
|
+
"status = "+req.status+".");
|
77
|
+
});
|
78
|
+
req.open("GET", "authenticate.json", true);
|
79
|
+
req.send();
|
80
|
+
}
|
81
|
+
|
82
|
+
/* Basic theory of operation:
|
83
|
+
* We use a complicated CORs setup to login to the frontend with an XHR
|
84
|
+
* request. Normally frontend API requests should be stateless, but if
|
85
|
+
* we pass ?request_cookie the frontend will assign a cookie with the
|
86
|
+
* screen token, under Concerto's domain, to the client browser (important
|
87
|
+
* because redirecting with basic auth is flaky). Now when we redirect to
|
88
|
+
* the frontend, the cookie is used and the screen is authenticated.
|
89
|
+
*/
|
90
|
+
function redirect_with_auth(url,user,pass) {
|
91
|
+
log("Attempting pre-authorization for redirect to "+url+".");
|
92
|
+
var req = new XMLHttpRequest();
|
93
|
+
auth_url = url + "?request_cookie";
|
94
|
+
// Note: Firefox will prevent creating the request object for
|
95
|
+
// cross-site requests if user and pass are given to open
|
96
|
+
req.open("GET", auth_url, true);
|
97
|
+
req.setRequestHeader("Authorization", "Basic " + btoa(user+":"+pass))
|
98
|
+
req.withCredentials = true;
|
99
|
+
req.addEventListener("load", function() {
|
100
|
+
if (req.status == 200) {
|
101
|
+
log("Pre-authorization appears successful.");
|
102
|
+
log("Redirecting to "+url+".");
|
103
|
+
document.location=url;
|
104
|
+
} else {
|
105
|
+
update_status("Error authenticating. Please ensuring Concerto is "+
|
106
|
+
"running properly.");
|
107
|
+
error("Pre-authorization request resulted in status "+req.status+
|
108
|
+
" from Concerto.");
|
109
|
+
}
|
110
|
+
});
|
111
|
+
req.addEventListener("error", function() {
|
112
|
+
update_status("Concerto Server Inaccessible. Make sure it is up.");
|
113
|
+
error("Pre-authentication request to Concerto errored out.");
|
114
|
+
});
|
115
|
+
req.send();
|
116
|
+
}
|
117
|
+
|
118
|
+
window.onload = function () {
|
119
|
+
log("Authentication Script Initializing");
|
120
|
+
var nojs=document.getElementById("no-js");
|
121
|
+
nojs.parentNode.removeChild(nojs);
|
122
|
+
|
123
|
+
// perform the first request ASAP
|
124
|
+
update_status("");
|
125
|
+
check_token();
|
126
|
+
|
127
|
+
// schedule polling in such a way that the user can see what's going on
|
128
|
+
checkIntHandle = window.setInterval(function(){
|
129
|
+
update_status("Updating...");
|
130
|
+
setTimeout(function() {
|
131
|
+
check_token()
|
132
|
+
},500);
|
133
|
+
}, 5000);
|
134
|
+
}
|
@@ -1,27 +1,27 @@
|
|
1
1
|
function show_hide(id_to_show, where) {
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
2
|
+
$(where).each(function(k, el) {
|
3
|
+
if ($(el).attr('id') == id_to_show) {
|
4
|
+
$(el).show( );
|
5
|
+
} else {
|
6
|
+
$(el).hide( );
|
7
|
+
}
|
8
|
+
});
|
9
9
|
}
|
10
10
|
|
11
11
|
function update_connection_type_fields() {
|
12
|
-
|
13
|
-
|
12
|
+
var id_to_show = $("#connection_type").val();
|
13
|
+
show_hide(id_to_show, "#connection div");
|
14
14
|
}
|
15
15
|
|
16
16
|
function update_address_type_fields() {
|
17
|
-
|
18
|
-
|
17
|
+
var id_to_show = $("#addressing_type").val();
|
18
|
+
show_hide(id_to_show, "#address div");
|
19
19
|
}
|
20
20
|
|
21
21
|
$(function() {
|
22
|
-
|
23
|
-
|
22
|
+
update_connection_type_fields( );
|
23
|
+
update_address_type_fields( );
|
24
24
|
|
25
|
-
|
26
|
-
|
25
|
+
$("#connection_type").change(update_connection_type_fields);
|
26
|
+
$("#addressing_type").change(update_address_type_fields);
|
27
27
|
});
|
@@ -0,0 +1,15 @@
|
|
1
|
+
%script{:src=>"authenticate.js"}
|
2
|
+
|
3
|
+
%h1 Concerto Player Authentication
|
4
|
+
|
5
|
+
#no-js
|
6
|
+
It looks like Javascript is disabled. The Concerto Display will not work.
|
7
|
+
|
8
|
+
- unless @display_temp_token.nil?
|
9
|
+
#instructions
|
10
|
+
Please enter the following token when setting up the screen in the
|
11
|
+
Concerto Panel:
|
12
|
+
= @display_temp_token
|
13
|
+
|
14
|
+
#status
|
15
|
+
Waiting for first update...
|
@@ -1,8 +1,11 @@
|
|
1
|
-
System Uptime: #{Uptime.uptime}
|
1
|
+
%p System Uptime: #{Uptime.uptime}
|
2
2
|
|
3
|
-
System Processes:
|
4
|
-
%
|
5
|
-
- for p in @proctable
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
%p System Processes:
|
4
|
+
%ul
|
5
|
+
- for p in @proctable
|
6
|
+
%li
|
7
|
+
%b= p.comm
|
8
|
+
(PID #{p.pid.to_s})
|
9
|
+
|
10
|
+
%p Screen on/off rules:
|
11
|
+
%pre= JSON.pretty_generate(@on_off_rules)
|
@@ -3,17 +3,22 @@
|
|
3
3
|
You're seeing this because your Concerto player has not yet been configured.
|
4
4
|
We need the URL of your Concerto instance before we can get up and running.
|
5
5
|
|
6
|
+
- unless @errors.nil?
|
7
|
+
- @errors.each do |error|
|
8
|
+
%p{:style=>'color:red'}= error
|
9
|
+
|
10
|
+
|
6
11
|
%form{:method=>'post'}
|
7
12
|
%p
|
8
13
|
%label{:for=>'url'} URL
|
9
|
-
%input{:type=>'text', :name=>'url'}
|
14
|
+
%input{:type=>'text', :name=>'url', :value=>@url}
|
10
15
|
%input{:type=>'submit', :value=>'Here it is!'}
|
11
16
|
|
12
17
|
%p
|
13
18
|
The IPv4 address of this screen appears to be:
|
14
19
|
=my_ip
|
15
20
|
%p
|
16
|
-
==This page can be accessed remotely via http://#{my_ip}/setup
|
21
|
+
==This page can be accessed remotely via http://#{my_ip}:#{my_port}/setup
|
17
22
|
%p
|
18
23
|
Username is root, default password is 'default'.
|
19
24
|
%p
|
@@ -4,45 +4,45 @@ require 'fileutils'
|
|
4
4
|
# A key/value store for strings.
|
5
5
|
# Right now implemented as disk files.
|
6
6
|
module Bandshell
|
7
|
-
|
8
|
-
|
7
|
+
module ConfigStore
|
8
|
+
@@path = nil
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
def self.read_config(name, default='')
|
11
|
+
initialize_path if not @@path
|
12
|
+
file = File.join(@@path, name)
|
13
|
+
rofile = File.join(@@ropath, name)
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
15
|
+
# Check the read/write config location first. If nothing there,
|
16
|
+
# check the read-only location. If nothing is there, return default.
|
17
|
+
# This way writes can be made at runtime on read-only media while
|
18
|
+
# still allowing some settings to be "baked into" the media.
|
19
|
+
if File.exist?(file)
|
20
|
+
IO.read(file)
|
21
|
+
elsif File.exist?(rofile)
|
22
|
+
IO.read(rofile)
|
23
|
+
else
|
24
|
+
default
|
25
|
+
end
|
26
|
+
end
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
28
|
+
# Write a config to the read/write configuration location.
|
29
|
+
def self.write_config(name, value)
|
30
|
+
initialize_path if not @@path
|
31
|
+
file = File.join(@@path, name)
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
33
|
+
File.open(file, 'w') do |f|
|
34
|
+
f.write value
|
35
|
+
end
|
36
|
+
end
|
37
37
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
38
|
+
def self.initialize_path
|
39
|
+
@@ropath = File.join(LiveImage.mountpoint, 'concerto', 'config')
|
40
|
+
if LiveImage.readonly?
|
41
|
+
@@path = '/tmp/concerto/config'
|
42
|
+
else
|
43
|
+
@@path = @@ropath
|
44
|
+
end
|
45
|
+
FileUtils.mkdir_p @@path
|
46
|
+
end
|
47
|
+
end
|
48
48
|
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require 'bandshell/config_store'
|
2
|
+
require 'net/http'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Bandshell
|
6
|
+
module HardwareApi
|
7
|
+
class << self
|
8
|
+
def temp_token
|
9
|
+
Bandshell::ConfigStore.read_config('auth_temp_token')
|
10
|
+
end
|
11
|
+
|
12
|
+
def auth_token
|
13
|
+
Bandshell::ConfigStore.read_config('auth_token')
|
14
|
+
end
|
15
|
+
|
16
|
+
def concerto_url
|
17
|
+
Bandshell::ConfigStore.read_config('concerto_url', '')
|
18
|
+
end
|
19
|
+
|
20
|
+
def frontend_uri
|
21
|
+
URI::join(concerto_url,'frontend')
|
22
|
+
end
|
23
|
+
|
24
|
+
def frontend_api_uri
|
25
|
+
URI::join(concerto_url,'frontend.json')
|
26
|
+
end
|
27
|
+
|
28
|
+
def have_temp_token?
|
29
|
+
!temp_token.empty?
|
30
|
+
end
|
31
|
+
|
32
|
+
def have_auth_token?
|
33
|
+
!auth_token.empty?
|
34
|
+
end
|
35
|
+
|
36
|
+
attr_reader :screen_id, :screen_url
|
37
|
+
|
38
|
+
def attempt_to_get_screen_data!
|
39
|
+
unless have_temp_token? or have_auth_token?
|
40
|
+
request_temp_token!
|
41
|
+
end
|
42
|
+
|
43
|
+
unless have_auth_token?
|
44
|
+
check_temp_token!
|
45
|
+
end
|
46
|
+
|
47
|
+
if have_auth_token?
|
48
|
+
status = fetch_screen_data
|
49
|
+
if status == :stat_badauth
|
50
|
+
ConfigStore.write_config('auth_token','')
|
51
|
+
request_temp_token!
|
52
|
+
end
|
53
|
+
else
|
54
|
+
status = :stat_err
|
55
|
+
end
|
56
|
+
status
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def clear_screen_data
|
62
|
+
@status = nil
|
63
|
+
@screen_url = nil
|
64
|
+
@screen_id =nil
|
65
|
+
end
|
66
|
+
|
67
|
+
# Get array of data about the screen from the server
|
68
|
+
# This can only succeed once we have obtained a valid auth token.
|
69
|
+
def fetch_screen_data
|
70
|
+
return nil if auth_token.empty?
|
71
|
+
|
72
|
+
response = get_with_auth(frontend_api_uri, 'screen', auth_token)
|
73
|
+
if response.nil?
|
74
|
+
clear_screen_data
|
75
|
+
return :stat_serverr
|
76
|
+
end
|
77
|
+
|
78
|
+
if response.code != "200"
|
79
|
+
clear_screen_data
|
80
|
+
return :stat_serverr
|
81
|
+
end
|
82
|
+
|
83
|
+
begin
|
84
|
+
data = JSON.parse(response.body)
|
85
|
+
if data.has_key? 'screen_id'
|
86
|
+
@screend_id = data['screen_id']
|
87
|
+
@screen_url = data['frontend_url']
|
88
|
+
return :stat_success
|
89
|
+
else
|
90
|
+
clear_screen_data
|
91
|
+
return :stat_badauth
|
92
|
+
end
|
93
|
+
rescue
|
94
|
+
clear_screen_data
|
95
|
+
return :stat_serverr
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
def get_with_auth(uri, user, pass)
|
101
|
+
begin
|
102
|
+
req = Net::HTTP::Get.new(uri.to_s)
|
103
|
+
req.basic_auth user, pass
|
104
|
+
res = Net::HTTP.start(uri.hostname, uri.port) { |http|
|
105
|
+
http.request(req)
|
106
|
+
}
|
107
|
+
rescue Errno::ECONNREFUSED
|
108
|
+
res = nil
|
109
|
+
end
|
110
|
+
res
|
111
|
+
end
|
112
|
+
|
113
|
+
def request_temp_token!
|
114
|
+
response = Net::HTTP.get_response(frontend_api_uri)
|
115
|
+
if response.code != "200"
|
116
|
+
return false
|
117
|
+
end
|
118
|
+
|
119
|
+
data=JSON.parse(response.body)
|
120
|
+
if data.has_key? 'screen_temp_token'
|
121
|
+
ConfigStore.write_config('auth_temp_token',data['screen_temp_token'])
|
122
|
+
return true
|
123
|
+
end
|
124
|
+
return false
|
125
|
+
end
|
126
|
+
|
127
|
+
def check_temp_token!
|
128
|
+
return false if temp_token.empty?
|
129
|
+
|
130
|
+
query = URI.join(frontend_api_uri,"?screen_temp_token="+temp_token)
|
131
|
+
response = Net::HTTP.get_response(query)
|
132
|
+
if response.code != "200"
|
133
|
+
return false
|
134
|
+
end
|
135
|
+
|
136
|
+
data=JSON.parse(response.body)
|
137
|
+
if data.has_key? 'screen_auth_token'
|
138
|
+
ConfigStore.write_config('auth_token',data['screen_auth_token'])
|
139
|
+
ConfigStore.write_config('auth_temp_token','')
|
140
|
+
return true
|
141
|
+
end
|
142
|
+
return false
|
143
|
+
end
|
144
|
+
|
145
|
+
public
|
146
|
+
|
147
|
+
# Fetch player settings from concerto-hardware
|
148
|
+
# TODO: clean up errors/ return values
|
149
|
+
def get_player_info
|
150
|
+
return nil if auth_token.empty?
|
151
|
+
|
152
|
+
# Try to do this in one GET.
|
153
|
+
player_info_uri = URI::join(concerto_url,'hardware/',
|
154
|
+
'players/','current.json')
|
155
|
+
|
156
|
+
response = get_with_auth(player_info_uri, 'screen', auth_token)
|
157
|
+
if response.nil?
|
158
|
+
return :stat_serverr
|
159
|
+
end
|
160
|
+
|
161
|
+
if response.code != "200"
|
162
|
+
return :stat_serverr
|
163
|
+
end
|
164
|
+
|
165
|
+
begin
|
166
|
+
data = JSON.parse(response.body)
|
167
|
+
if data.has_key? 'screen_on_off'
|
168
|
+
# We actually got some data
|
169
|
+
return data
|
170
|
+
else
|
171
|
+
return :stat_badauth
|
172
|
+
end
|
173
|
+
rescue
|
174
|
+
return :stat_serverr
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
end # class << self
|
179
|
+
end # module HardwareApi
|
180
|
+
end # module Bandshell
|