diff --git a/admin/app/admin/jamblaster.rb b/admin/app/admin/jamblaster.rb new file mode 100644 index 000000000..33f48100d --- /dev/null +++ b/admin/app/admin/jamblaster.rb @@ -0,0 +1,16 @@ +ActiveAdmin.register JamRuby::Jamblaster, :as => 'Jamblaster' do + + + menu :label => 'JamBlasters', :parent => 'JamBlaster' + + form do |f| + f.inputs 'New JamBlaster' do + f.input :user, required: true, collection: User.all, include_blank: false + f.input :serial_no, required: true + f.input :client_id, required: false + f.input :vtoken, required: false + f.input :users, required: true, collection: User.all, include_blank: false + end + f.actions + end +end diff --git a/db/manifest b/db/manifest index 89fd895c4..c3312734b 100755 --- a/db/manifest +++ b/db/manifest @@ -328,4 +328,5 @@ populate_subjects.sql reviews.sql download_tracker_fingerprints.sql connection_active.sql -chat_channel.sql \ No newline at end of file +chat_channel.sql +jamblaster.sql \ No newline at end of file diff --git a/db/up/jamblaster.sql b/db/up/jamblaster.sql new file mode 100644 index 000000000..56760576a --- /dev/null +++ b/db/up/jamblaster.sql @@ -0,0 +1,28 @@ +CREATE TABLE jamblasters ( + id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4() NOT NULL, + user_id VARCHAR(64) NOT NULL REFERENCES users(id) ON DELETE SET NULL, + serial_no VARCHAR(1000) UNIQUE, + vtoken VARCHAR(1000) UNIQUE, + client_id VARCHAR(64) UNIQUE, + created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL, + updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL +); + +CREATE TABLE jamblasters_users ( + id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4() NOT NULL, + user_id VARCHAR(64) NOT NULL REFERENCES users(id) ON DELETE CASCADE, + jamblaster_id VARCHAR(64) NOT NULL REFERENCES jamblasters(id) ON DELETE CASCADE, + created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL, + updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL +); + +CREATE TABLE jamblaster_pairing_requests ( + id VARCHAR(64) PRIMARY KEY DEFAULT uuid_generate_v4() NOT NULL, + user_id VARCHAR(64) NOT NULL REFERENCES users(id) ON DELETE CASCADE, + jamblaster_id VARCHAR(64) NOT NULL REFERENCES jamblasters(id) ON DELETE CASCADE, + jamblaster_client_id VARCHAR(64) NOT NULL, + sibling_client_id VARCHAR(64) NOT NULL, + sibling_key VARCHAR(1000) NOT NULL, + created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL, + updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL +); diff --git a/pb/src/client_container.proto b/pb/src/client_container.proto index 250bede8e..4e7d035e5 100644 --- a/pb/src/client_container.proto +++ b/pb/src/client_container.proto @@ -102,6 +102,9 @@ message ClientMessage { RESTART_APPLICATION = 403; STOP_APPLICATION = 404; + // jamblaster messages + PAIR_ATTEMPT = 500; + SERVER_BAD_STATE_RECOVERED = 900; SERVER_GENERIC_ERROR = 1000; @@ -217,6 +220,9 @@ message ClientMessage { optional RestartApplication restart_application = 403; optional StopApplication stop_application = 404; + // JamBlaster messages + optional PairAttempt pair_attempt = 500; + // Server-to-Client special messages optional ServerBadStateRecovered server_bad_state_recovered = 900; @@ -750,6 +756,11 @@ message StopApplication { } +message PairAttempt { + optional string scid = 1; + optional string vtoken = 2; +} + // route_to: client // this should follow a ServerBadStateError in the case that the // websocket gateway recovers from whatever ailed it diff --git a/ruby/lib/jam_ruby.rb b/ruby/lib/jam_ruby.rb index d655cff1e..996a0714f 100755 --- a/ruby/lib/jam_ruby.rb +++ b/ruby/lib/jam_ruby.rb @@ -264,6 +264,9 @@ require "jam_ruby/models/gift_card" require "jam_ruby/models/gift_card_purchase" require "jam_ruby/models/gift_card_type" require "jam_ruby/models/jam_track_session" +require "jam_ruby/models/jamblaster" +require "jam_ruby/models/jamblaster_user" +require "jam_ruby/models/jamblaster_pairing_request" include Jampb diff --git a/ruby/lib/jam_ruby/message_factory.rb b/ruby/lib/jam_ruby/message_factory.rb index dff7e726b..6297b0b23 100644 --- a/ruby/lib/jam_ruby/message_factory.rb +++ b/ruby/lib/jam_ruby/message_factory.rb @@ -921,6 +921,18 @@ module JamRuby ) end + def pair_attempt(jbid, scid, vtoken) + pair_attempt = Jampb::PairAttempt.new( + :scid => scid, + :vtoken => vtoken + ) + + Jampb::ClientMessage.new( + :type => ClientMessage::Type::PAIR_ATTEMPT, + :route_to => CLIENT_TARGET_PREFIX + jbid, + :pair_attempt => pair_attempt + ) + end # create a musician fresh session message def musician_session_fresh(session_id, user_id, username, photo_url) fresh = Jampb::MusicianSessionFresh.new( diff --git a/ruby/lib/jam_ruby/models/jamblaster.rb b/ruby/lib/jam_ruby/models/jamblaster.rb new file mode 100644 index 000000000..c8aa44571 --- /dev/null +++ b/ruby/lib/jam_ruby/models/jamblaster.rb @@ -0,0 +1,39 @@ +module JamRuby + class Jamblaster < ActiveRecord::Base + + attr_accessible :user_id, :serial_no, :client_id, :vtoken, :user_ids, as: :admin + + + belongs_to :user, class_name: 'JamRuby::User' + has_many :jamblasters_users, class_name: "JamRuby::JamblasterUser" + has_many :users, class_name: 'JamRuby::User', through: :jamblasters_users + has_many :jamblaster_pairing_requests, class_name: "JamRuby::JamblasterPairingRequest", foreign_key: :jamblaster_id + + validates :user, presence: true + + validates :serial_no, uniqueness: true + validates :vtoken, uniqueness: true + validates :client_id, uniqueness: true + + before_save :sanitize_active_admin + + def sanitize_active_admin + self.vtoken = nil if self.vtoken == '' + self.client_id = nil if self.client_id == '' + end + + class << self + + @@mq_router = MQRouter.new + @@message_factory = MessageFactory.new + + def send_pair_attempt(jbid, scid, vtoken) + msg = @@message_factory.pair_attempt( + jbid, scid, vtoken + ) + + @@mq_router.publish_to_client(jbid, msg, {:client_id => scid}) + end + end + end +end diff --git a/ruby/lib/jam_ruby/models/jamblaster_pairing_request.rb b/ruby/lib/jam_ruby/models/jamblaster_pairing_request.rb new file mode 100644 index 000000000..682c34314 --- /dev/null +++ b/ruby/lib/jam_ruby/models/jamblaster_pairing_request.rb @@ -0,0 +1,14 @@ +module JamRuby + class JamblasterPairingRequest < ActiveRecord::Base + + belongs_to :user, class_name: 'JamRuby::User' + belongs_to :jamblaster, class_name: 'JamRuby::Jamblaster', foreign_key: :jamblaster_id + + validates :user, presence: true + validates :jamblaster, presence: true + validates :jamblaster_client_id, presence: true + validates :sibling_client_id, presence: true + validates :sibling_key, presence: true + + end +end diff --git a/ruby/lib/jam_ruby/models/jamblaster_user.rb b/ruby/lib/jam_ruby/models/jamblaster_user.rb new file mode 100644 index 000000000..97718c9df --- /dev/null +++ b/ruby/lib/jam_ruby/models/jamblaster_user.rb @@ -0,0 +1,11 @@ +module JamRuby + class JamblasterUser < ActiveRecord::Base + self.table_name = "jamblasters_users" + + belongs_to :jamblaster, class_name: "JamRuby::Jamblaster" + belongs_to :user, class_name: "JamRuby::User" + + validates :jamblaster, presence:true + validates :user, presence: true + end +end diff --git a/ruby/lib/jam_ruby/models/user.rb b/ruby/lib/jam_ruby/models/user.rb index c99b36bcd..a2c2330f4 100644 --- a/ruby/lib/jam_ruby/models/user.rb +++ b/ruby/lib/jam_ruby/models/user.rb @@ -191,6 +191,9 @@ module JamRuby has_many :jam_track_session, :class_name => "JamRuby::JamTrackSession" + has_many :jamblasters_users, class_name: "JamRuby::JamblasterUser" + has_many :jamblasters, class_name: 'JamRuby::Jamblaster', through: :jamblasters_users + before_save :default_anonymous_names before_save :create_remember_token, :if => :should_validate_password? before_save :stringify_avatar_info, :if => :updating_avatar diff --git a/ruby/spec/factories.rb b/ruby/spec/factories.rb index dd4781571..eb629c4de 100644 --- a/ruby/spec/factories.rb +++ b/ruby/spec/factories.rb @@ -888,4 +888,23 @@ FactoryGirl.define do association :user, factory: :user end + factory :jamblaster, class: 'JamRuby::Jamblaster' do + + association :user, factory: :user + + sequence(:serial_no ) { |n| "serial_no#{n}" } + sequence(:vtoken ) { |n| "vtoken#{n}" } + sequence(:client_id ) { |n| "client_id#{n}" } + end + + factory :jamblaster_pairing_request, class: 'JamRuby::JamblasterPairingRequest' do + + association :user, factory: :user + association :jamblaster, factory: :jamblaster + + sequence(:jamblaster_client_id ) { |n| "jamblaster_client_id#{n}" } + sequence(:sibling_client_id ) { |n| "sibling_client_id#{n}" } + sequence(:sibling_key ) { |n| "sibling_key#{n}" } + end end + diff --git a/ruby/spec/jam_ruby/models/jamblaster_spec.rb b/ruby/spec/jam_ruby/models/jamblaster_spec.rb new file mode 100644 index 000000000..a1e1370ad --- /dev/null +++ b/ruby/spec/jam_ruby/models/jamblaster_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe Jamblaster do + + let(:jamblaster) {FactoryGirl.create(:jamblaster)} + let(:user) {FactoryGirl.create(:user)} + + it "can be created" do + FactoryGirl.create(:jamblaster) + end + + it "can associate to users" do + jamblaster.users.should eq([]) + user.jamblasters.should eq([]) + + end +end \ No newline at end of file diff --git a/web/app/controllers/api_jamblasters_controller.rb b/web/app/controllers/api_jamblasters_controller.rb new file mode 100644 index 000000000..9ecf43923 --- /dev/null +++ b/web/app/controllers/api_jamblasters_controller.rb @@ -0,0 +1,100 @@ +class ApiJamblastersController < ApiController + + before_filter :api_signed_in_user, except: [:login, :store_token] + respond_to :json + + def get_tokens + @jamblasters = current_user.jamblasters + end + + def start_pairing + jamblaster = Jamblaster.find_by_client_id(params[:jbid]) + + if jamblaster && !current_user.jamblasters.include?(jamblaster) + render :json => {reason: "jamblaster_access", message: "current user does not have access to jamblaster #{jamblaster.id}"}, status: 403 + return + end + + @pairing = JamblasterPairingRequest.new + @pairing.user = current_user + @pairing.jamblaster_client_id = params[:jbid] + @pairing.jamblaster = jamblaster + @pairing.sibling_client_id = params[:scid] + @pairing.sibling_key = params[:key] + if !@pairing.save + respond_with_model(@pairing) + else + + end + end + + def login + scid = params[:scid] + jbid = params[:jbid] + key = params[:key] + serial_no = params[:serial_no] + pairing_request = JamblasterPairingRequest.where(jamblaster_client_id: jbid).where(sibling_client_id: scid).where(sibling_key: key).first + jamblaster = Jamblaster.find_by_serial_no(serial_no) + + if jamblaster.nil? + render :json => { :message => 'No jamblaster found with serial_no ' + serial_no, reason: "serial_no" }, :status => 404 + return + end + + if pairing_request.nil? + render :json => { :message => "No pairing request found with jbid=#{jbid} && sibling_client_id=#{scid} && sibling_key=#{key}", reason: "no_pairing_request" }, :status => 404 + return + end + + render :json => {remember_token: pairing_request.user.remember_token}, :status => 200 + end + + def store_token + vtoken = params[:vtoken] + scid = params[:scid] + jbid = params[:jbid] + key = params[:key] + + pairing_request = JamblasterPairingRequest.where(jamblaster_client_id: jbid).where(sibling_client_id: scid).where(sibling_key: key).first + if pairing_request.nil? + render :json => { :message => "No pairing request found with jbid=#{jbid} && sibling_client_id=#{scid} && sibling_key=#{key}", reason: "no_pairing_request" }, :status => 404 + return + end + + if vtoken.blank? + render :json => { :errors => { vtoken: ['is empty'] } }, :status => 422 + return + end + + @jamblaster = pairing_request.jamblaster + @jamblaster.vtoken = vtoken + if !@jamblaster.save + respond_with_model(@jamblaster) + else + + end + + end + + def pair + vtoken = params[:vtoken] + scid = params[:scid] + jbid = params[:jbid] + + jamblaster = Jamblaster.find_by_vtoken(vtoken) + + if jamblaster.nil? + render :json => {reason: "no_vtoken", message: "No jamblaster found with vtoken:#{vtoken}" }, status: 404 + return + end + + if !current_user.jamblasters.include?(jamblaster) + render :json => {reason: "jamblaster_access", message: "current user does not have access to jamblaster #{jamblaster.id} with vtoken #{vtoken}"}, status: 403 + return + end + + Jamblaster.send_pair_attempt(jbid, scid, vtoken) + + @jamblaster = jamblaster + end +end diff --git a/web/app/views/api_jamblasters/get_tokens.rabl b/web/app/views/api_jamblasters/get_tokens.rabl new file mode 100644 index 000000000..6fdfa70ef --- /dev/null +++ b/web/app/views/api_jamblasters/get_tokens.rabl @@ -0,0 +1,4 @@ +object @jamblasters + + +attributes :id, :serial_no, :client_id, :vtoken \ No newline at end of file diff --git a/web/app/views/api_jamblasters/login.rabl b/web/app/views/api_jamblasters/login.rabl new file mode 100644 index 000000000..e69de29bb diff --git a/web/app/views/api_jamblasters/pair.rabl b/web/app/views/api_jamblasters/pair.rabl new file mode 100644 index 000000000..12de7ee12 --- /dev/null +++ b/web/app/views/api_jamblasters/pair.rabl @@ -0,0 +1,3 @@ +object @jamblaster + +attributes :id \ No newline at end of file diff --git a/web/app/views/api_jamblasters/start_pairing.rabl b/web/app/views/api_jamblasters/start_pairing.rabl new file mode 100644 index 000000000..e9683b0b0 --- /dev/null +++ b/web/app/views/api_jamblasters/start_pairing.rabl @@ -0,0 +1,4 @@ +object @pairing + + +attributes :id \ No newline at end of file diff --git a/web/app/views/api_jamblasters/store_token.rabl b/web/app/views/api_jamblasters/store_token.rabl new file mode 100644 index 000000000..12de7ee12 --- /dev/null +++ b/web/app/views/api_jamblasters/store_token.rabl @@ -0,0 +1,3 @@ +object @jamblaster + +attributes :id \ No newline at end of file diff --git a/web/config/routes.rb b/web/config/routes.rb index 36643a5e2..b0f8829f3 100644 --- a/web/config/routes.rb +++ b/web/config/routes.rb @@ -675,5 +675,11 @@ SampleApp::Application.routes.draw do match '/links/jamkazam' => 'api_links#jamkazam_general_index' match '/links/sessions' => 'api_links#session_index' match '/links/recordings' => 'api_links#recording_index' + + match 'jamblasters/pairing/tokens' => 'api_jamblasters#get_tokens', :via => :get + match 'jamblasters/pairing/start' => 'api_jamblasters#start_pairing', :via => :post + match 'jamblasters/pairing/login' => 'api_jamblasters#login', :via => :post + match 'jamblasters/pairing/store' => 'api_jamblasters#store_token', :via => :post + match 'jamblasters/pairing/pair' => 'api_jamblasters#pair', :via => :post end end diff --git a/web/spec/controllers/api_jamblasters_controller_spec.rb b/web/spec/controllers/api_jamblasters_controller_spec.rb new file mode 100644 index 000000000..20dfaffea --- /dev/null +++ b/web/spec/controllers/api_jamblasters_controller_spec.rb @@ -0,0 +1,211 @@ +require 'spec_helper' +describe ApiJamblastersController do + render_views + + let(:user) {FactoryGirl.create(:user)} + let(:jamblaster) { FactoryGirl.create(:jamblaster, user: user) } + + before(:each) do + JamblasterUser.delete_all + JamblasterPairingRequest.delete_all + Jamblaster.delete_all + end + + describe "get_tokens" do + before(:each) { + controller.current_user = user + } + + it "works" do + get :get_tokens, {:format=>'json' } + response.status.should == 200 + json = JSON.parse(response.body) + json.length.should eq(0) + + # associate Jamblaster + jamblaster = FactoryGirl.create(:jamblaster, user: user) + + user.jamblasters << jamblaster + user.save! + + get :get_tokens, {:format=>'json' } + response.status.should == 200 + json = JSON.parse(response.body) + json.length.should eq(1) + end + end + + describe "start_pairing" do + + before(:each) do + controller.current_user = user + user.jamblasters << jamblaster + user.save! + end + + it "works" do + post :start_pairing, {:format=>'json', jbid: jamblaster.client_id, scid: 'sibling_id', key: 'sibling_key'} + json = JSON.parse(response.body) + response.status.should == 200 + + request = JamblasterPairingRequest.where(jamblaster_id: jamblaster.id).first + request.should_not be_nil + request.user.should eql(user) + request.sibling_key.should eq 'sibling_key' + request.sibling_client_id.should eq 'sibling_id' + end + + it "returns 422 if bogus jamblaster" do + post :start_pairing, {:format=>'json', jbid: 'nada', scid: 'sibling_id', key: 'sibling_key'} + json = JSON.parse(response.body) + response.status.should == 422 + json = JSON.parse(response.body) + end + + it "returns 422 if restart pairing" do + + end + end + + describe "login" do + + before(:each) do + controller.current_user = user + user.jamblasters << jamblaster + user.save! + end + + it "works" do + post :start_pairing, {:format=>'json', jbid: jamblaster.client_id, scid: 'sibling_id2', key: 'sibling_key2'} + response.status.should == 200 + + request = JamblasterPairingRequest.where(jamblaster_client_id: jamblaster.client_id, sibling_key: 'sibling_key2', sibling_client_id: 'sibling_id2').first + request.should_not be_nil + request.user.should eql(user) + request.sibling_key.should eq 'sibling_key2' + request.sibling_client_id.should eq 'sibling_id2' + request.jamblaster_client_id.should eq jamblaster.client_id + + post :login, {:format=>'json', jbid: jamblaster.client_id, serial_no: jamblaster.serial_no, scid: 'sibling_id2', key: 'sibling_key2'} + json = JSON.parse(response.body) + response.status.should == 200 + json['remember_token'].should eq(user.remember_token) + end + end + + describe "store_token" do + + before(:each) do + controller.current_user = user + user.jamblasters << jamblaster + user.save! + end + + it "works" do + post :start_pairing, {:format=>'json', jbid: jamblaster.client_id, scid: 'sibling_id3', key: 'sibling_key3'} + response.status.should == 200 + + request = JamblasterPairingRequest.where(jamblaster_client_id: jamblaster.client_id, sibling_key: 'sibling_key3', sibling_client_id: 'sibling_id3').first + request.should_not be_nil + request.user.should eql(user) + request.sibling_key.should eq 'sibling_key3' + request.sibling_client_id.should eq 'sibling_id3' + request.jamblaster_client_id.should eq jamblaster.client_id + + post :login, {:format=>'json', jbid: jamblaster.client_id, serial_no: jamblaster.serial_no, scid: 'sibling_id3', key: 'sibling_key3'} + json = JSON.parse(response.body) + response.status.should == 200 + json['remember_token'].should eq(user.remember_token) + + post :store_token, {:format => 'json', vtoken: 'vtoken1', scid: 'sibling_id3', jbid: jamblaster.client_id, key: 'sibling_key3'} + json = JSON.parse(response.body) + response.status.should == 200 + json['id'].should eq(jamblaster.id) + end + end + + describe "pair" do + before(:each) do + controller.current_user = user + user.jamblasters << jamblaster + user.save! + end + + it "works" do + + post :start_pairing, {:format=>'json', jbid: jamblaster.client_id, scid: 'sibling_id4', key: 'sibling_key4'} + response.status.should == 200 + + request = JamblasterPairingRequest.where(jamblaster_client_id: jamblaster.client_id, sibling_key: 'sibling_key4', sibling_client_id: 'sibling_id4').first + request.should_not be_nil + request.user.should eql(user) + request.sibling_key.should eq 'sibling_key4' + request.sibling_client_id.should eq 'sibling_id4' + request.jamblaster_client_id.should eq jamblaster.client_id + + post :login, {:format=>'json', jbid: jamblaster.client_id, serial_no: jamblaster.serial_no, scid: 'sibling_id4', key: 'sibling_key4'} + json = JSON.parse(response.body) + response.status.should == 200 + json['remember_token'].should eq(user.remember_token) + + post :store_token, {:format => 'json', vtoken: 'vtoken2', scid: 'sibling_id4', jbid: jamblaster.client_id, key: 'sibling_key4'} + json = JSON.parse(response.body) + response.status.should == 200 + json['id'].should eq(jamblaster.id) + + get :get_tokens, {:format=>'json' } + response.status.should == 200 + json = JSON.parse(response.body) + json.length.should eq(1) + vtoken = json[0]["vtoken"] + vtoken.should eq("vtoken2") + + post :pair, {:format => 'json', vtoken: 'vtoken2', scid: 'sibling_id4', jbid: jamblaster.client_id} + response.status.should == 200 + json = JSON.parse(response.body) + json["id"].should eq jamblaster.id + end + end + + describe "logged in" do + before(:each) do + controller.current_user = user + end + + it "get_tokens" do + get :get_tokens, {:format=>'json' } + response.status.should == 200 + end + + it "start_pairing" do + post :start_pairing, {:format=>'json'} + response.status.should == 422 + end + + it "pair" do + post :pair, {:format=>'json'} + response.status.should == 404 + end + end + + describe "not logged in" do + before(:each) do + controller.current_user = nil + end + + it "get_tokens" do + get :get_tokens, {:format=>'json'} + response.status.should == 403 + end + + it "start_pairing" do + post :start_pairing, {:format=>'json'} + response.status.should == 403 + end + + it "pair" do + post :pair, {:format=>'json'} + response.status.should == 403 + end + end +end diff --git a/web/spec/factories.rb b/web/spec/factories.rb index a7816c972..ebf0fbe84 100644 --- a/web/spec/factories.rb +++ b/web/spec/factories.rb @@ -856,4 +856,23 @@ FactoryGirl.define do sequence(:code) {|n| n.to_s} card_type GiftCard::JAM_TRACKS_5 end + + factory :jamblaster, class: 'JamRuby::Jamblaster' do + + association :user, factory: :user + + sequence(:serial_no ) { |n| "serial_no#{n}" } + sequence(:vtoken ) { |n| "vtoken#{n}" } + sequence(:client_id ) { |n| "client_id#{n}" } + end + + factory :jamblaster_pairing_request, class: 'JamRuby::JamblasterPairingRequest' do + + association :user, factory: :user + association :jamblaster, factory: :jamblaster + + sequence(:jamblaster_client_id ) { |n| "jamblaster_client_id#{n}" } + sequence(:sibling_client_id ) { |n| "sibling_client_id#{n}" } + sequence(:sibling_key ) { |n| "sibling_key#{n}" } + end end