require "opentok/constants" require "opentok/session" require "base64" require "addressable/uri" require "openssl" require "active_support/time" module OpenTok # @private module TokenGenerator # this works when using include TokenGenerator def self.included(base) base.extend(ClassMethods) end module ClassMethods # @private arguments the method we should generate will need (in order): # * api_key (required - if no lambda assigned, part of method sig) # * api_secret (required - if no lambda assigned, part of method sig) # * session_id (required - if no lambda assigned, part of method sig) # * token_opts (optional - part of method sig) # # arg_lambdas is a hash of keys which are the above args and values are lambdas that all have the # signature ->(instance) def generates_tokens(arg_lambdas={}) @arg_lambdas = arg_lambdas define_method(:generate_token) do |*args| # puts "generate_something is being called on #{self}. set up with #{method_opts.inspect}" dynamic_args = [ :api_key, :api_secret, :session_id, :token_opts ].map do |arg| self.class.arg_lambdas[arg].call(self) if self.class.arg_lambdas[arg] end dynamic_args.compact! args = args.first(4-dynamic_args.length) self.class.generate_token.call(*dynamic_args, *args) end end # @private For internal use by the SDK. def arg_lambdas @arg_lambdas end # Generates a token def generate_token TokenGenerator::GENERATE_TOKEN_LAMBDA end end # @private TODO: this probably doesn't need to be a constant anyone can read GENERATE_TOKEN_LAMBDA = ->(api_key, api_secret, session_id, opts = {}) do # normalize required data params role = opts.fetch(:role, :publisher) unless ROLES.has_key? role raise "'#{role}' is not a recognized role" end unless Session.belongs_to_api_key? session_id.to_s, api_key raise "Cannot generate token for a session_id that doesn't belong to api_key: #{api_key}" end # minimum data params data_params = { :role => role, :session_id => session_id, :create_time => Time.now.to_i, :nonce => Random.rand } # normalize and add additional data params unless (expire_time = opts[:expire_time].to_i) == 0 unless expire_time.between?(Time.now.to_i, (Time.now + 30.days).to_i) raise "Expire time must be within the next 30 days" end data_params[:expire_time] = expire_time.to_i end unless opts[:data].nil? unless (data = opts[:data].to_s).length < 1000 raise "Connection data must be less than 1000 characters" end data_params[:connection_data] = data end if opts[:initial_layout_class_list] if opts[:initial_layout_class_list].is_a?(Array) data_params[:initial_layout_class_list] = opts[:initial_layout_class_list].join(' ') else data_params[:initial_layout_class_list] = opts[:initial_layout_class_list].to_s end end digest = OpenSSL::Digest.new('sha1') data_string = Addressable::URI.form_encode data_params meta_string = Addressable::URI.form_encode({ :partner_id => api_key, :sig => OpenSSL::HMAC.hexdigest(digest, api_secret, data_string) }) TOKEN_SENTINEL + Base64.strict_encode64(meta_string + ":" + data_string) end # this works when using extend TokenGenerator # def generates_tokens(method_opts) # puts "I'm being called on #{self} with argument #{method_opts.inspect}" # # end end end