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.
@@ -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