bandshell 0.0.20 → 0.0.21

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,2 +1,5 @@
1
+ path= File.join(File.dirname(__FILE__), '..', '..')
2
+ $LOAD_PATH.unshift(path)
3
+
1
4
  require './app.rb'
2
- run Sinatra::Application
5
+ run ConcertoConfigServer
@@ -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
- $(where).each(function(k, el) {
3
- if ($(el).attr('id') == id_to_show) {
4
- $(el).show( );
5
- } else {
6
- $(el).hide( );
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
- var id_to_show = $("#connection_type").val();
13
- show_hide(id_to_show, "#connection div");
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
- var id_to_show = $("#addressing_type").val();
18
- show_hide(id_to_show, "#address div");
17
+ var id_to_show = $("#addressing_type").val();
18
+ show_hide(id_to_show, "#address div");
19
19
  }
20
20
 
21
21
  $(function() {
22
- update_connection_type_fields( );
23
- update_address_type_fields( );
22
+ update_connection_type_fields( );
23
+ update_address_type_fields( );
24
24
 
25
- $("#connection_type").change(update_connection_type_fields);
26
- $("#addressing_type").change(update_address_type_fields);
25
+ $("#connection_type").change(update_connection_type_fields);
26
+ $("#addressing_type").change(update_address_type_fields);
27
27
  });
@@ -1,7 +1,7 @@
1
1
  function redirect() {
2
- window.location = '/screen';
2
+ window.location = '/screen';
3
3
  }
4
4
 
5
5
  $(function() {
6
- setTimeout(redirect, 5000);
6
+ setTimeout(redirect, 5000);
7
7
  })
@@ -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,4 +1,4 @@
1
- %htmlt
1
+ %html
2
2
  %head
3
3
  %title="Concerto Player Network Configuration"
4
4
  %script{:src=>"https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.js"}
@@ -1,8 +1,11 @@
1
- System Uptime: #{Uptime.uptime}
1
+ %p System Uptime: #{Uptime.uptime}
2
2
 
3
- System Processes:
4
- %br/
5
- - for p in @proctable
6
- %b= p.comm
7
- (PID #{p.pid.to_s})
8
- %br/
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
- module ConfigStore
8
- @@path = nil
7
+ module ConfigStore
8
+ @@path = nil
9
9
 
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)
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
- # 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
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
- # 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)
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
- File.open(file, 'w') do |f|
34
- f.write value
35
- end
36
- end
33
+ File.open(file, 'w') do |f|
34
+ f.write value
35
+ end
36
+ end
37
37
 
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
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