bandshell 0.0.20 → 0.0.21
Sign up to get free protection for your applications and to get access to all the features.
- 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
|