random extra websocket connection to phoenix app? - websocket

Simple user websocket with a single channel. I copied this code word for almost out of the how to section in the phoenix guidelines.
The first request is mine - it contains the user auth token from a facebook login response.
As you can see this comes from phoenix.js file and it works just fine... I am able to send and receive messages - no problem.
The second seems to be coming from somewhere else entirely and I have no clue why!?
frame.js which is not a file of mine, so must be part of some sort of node_module dependancy, The js is condensed into one line and not exactly legible.
I also get this in the logs every 5 seconds or so:
phoenix_1 | [info] CONNECT GametimeWeb.UserSocket
phoenix_1 | Transport: :websocket
phoenix_1 | Connect Info: %{}
phoenix_1 | Parameters: %{"token" => "", "vsn" => "2.0.0"}
phoenix_1 | :invalid - this is the response the socket returns I have jsut inspected it and printed to logs.
phoenix_1 | [debug] invalid
phoenix_1 | [info] Replied GametimeWeb.UserSocket :error
What am I doing wrong here?
Phoenix 1.4.10
UserSocket:
defmodule GametimeWeb.UserSocket do
use Phoenix.Socket
require Logger
## Channels
channel "sports:*", GametimeWeb.SportsChannel
# Socket params are passed from the client and can
# be used to verify and authenticate a user. After
# verification, you can put default assigns into
# the socket that will be set for all channels, ie
#
# {:ok, assign(socket, :user_id, verified_user_id)}
#
# To deny connection, return `:error`.
#
def connect(%{"token" => token}, socket) do
# max_age: 1209600 is equivalent to two weeks in seconds
case Phoenix.Token.verify(socket, "user socket salt", token, max_age: 1209600) do
{:ok, user_id} ->
{:ok, assign(socket, :user, user_id)}
{:error, reason} ->
Logger.debug IO.inspect(reason)
:error
end
end
endpoint code:
defmodule GametimeWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :gametime
socket "/socket", GametimeWeb.UserSocket,
websocket: true, # or list of options
longpoll: false

That is the helper socket for the live reload feature. It won't be opened when MIX_ENV is prod.

Related

Using ADAL with Logstash Custom Plugin const errors

I'm working to create a Logstash Input plugin to utilize ADAL for integration with the Office 365 Management Activity API's. I've written the individual components to get a token, use that token to subscribe, and to pull activity log data.
Now I'm working to integrate into the Logstash framework, and running into issues where Logstash is complaining that it's doesn't know what ADAL is, even though I have it required.
All the same code works independently outside of Logstash, just not within the plugin class.
This is my first foray into Ruby, so I'm pretty stumped. Any help?
Error message from Logstash:
[2018-09-16T00:51:32,816][INFO ][logstash.pipeline ] Starting pipeline {:pipeline_id=>"main", "pipeline.workers"=>8, "pipeline.batch.size"=>125, "pipeline.batch.delay"=>50}
[2018-09-16T00:51:33,921][INFO ][logstash.inputs.office365managementapi] Starting Office 365 Management API input...
[2018-09-16T00:51:34,246][ERROR][logstash.pipeline ] Error registering plugin {:pipeline_id=>"main", :plugin=>"<LogStash::Inputs::Office365ManagementApi client_id=>\"redacted\", tenant_id=>\"redacted\", tenant_domain=>\"redacted\", private_key=>\"/tmp/o365.pfx\", subscriptions=>[\"Audit.AzureActiveDirectory\", \"Audit.Exchange\", \"Audit.SharePoint\", \"Audit.General\", \"DLP.All\"], id=>\"fb61b83b76494f098a0a7e24391779ee1212f0d9adf8ef8dedae4424e8dedb57\", enable_metric=>true, codec=><LogStash::Codecs::Plain id=>\"plain_c7c9d514-5d23-459d-98ea-87d250e7a00c\", enable_metric=>true, charset=>\"UTF-8\">, resource=>\"https://manage.office.com\">", :error=>"uninitialized constant LogStash::Inputs::Office365ManagementApi::ADAL::Logging\nDid you mean? LogStash::Logging", :thread=>"#<Thread:0xca2e135 run>"}
[2018-09-16T00:51:34,367][ERROR][logstash.pipeline ] Pipeline aborted due to error {:pipeline_id=>"main", :exception=>#<NameError: uninitialized constant LogStash::Inputs::Office365ManagementApi::ADAL::Logging
Did you mean? LogStash::Logging>, :backtrace=>["org/jruby/RubyModule.java:3343:in `const_missing'", "/usr/local/Cellar/logstash/6.2.4/libexec/vendor/local_gems/82bdbf8d/logstash-input-office365_management_api-1.0.0/lib/logstash/inputs/office365_management_api.rb:70:in `register'", "/usr/local/Cellar/logstash/6.2.4/libexec/logstash-core/lib/logstash/pipeline.rb:342:in `register_plugin'", "/usr/local/Cellar/logstash/6.2.4/libexec/logstash-core/lib/logstash/pipeline.rb:353:in `block in register_plugins'", "org/jruby/RubyArray.java:1734:in `each'", "/usr/local/Cellar/logstash/6.2.4/libexec/logstash-core/lib/logstash/pipeline.rb:353:in `register_plugins'", "/usr/local/Cellar/logstash/6.2.4/libexec/logstash-core/lib/logstash/pipeline.rb:500:in `start_inputs'", "/usr/local/Cellar/logstash/6.2.4/libexec/logstash-core/lib/logstash/pipeline.rb:394:in `start_workers'", "/usr/local/Cellar/logstash/6.2.4/libexec/logstash-core/lib/logstash/pipeline.rb:290:in `run'", "/usr/local/Cellar/logstash/6.2.4/libexec/logstash-core/lib/logstash/pipeline.rb:250:in `block in start'"], :thread=>"#<Thread:0xca2e135 run>"}
[2018-09-16T00:51:34,418][ERROR][logstash.agent ] Failed to execute action {:id=>:main, :action_type=>LogStash::ConvergeResult::FailedAction, :message=>"Could not execute action: LogStash::PipelineAction::Create/pipeline_id:main, action_result: false", :backtrace=>nil}
Code is below:
# encoding: utf-8
require "logstash/inputs/base"
require "logstash/namespace"
require "stud/interval"
require "socket" # for Socket.gethostname
require "json"
require 'net/http'
require 'uri'
# Using this input you can receive activities from the Office 365 Management API
# ==== Security
# This plugin utilizes certificate authentication with the Office 365 Management API
# to generate an access token, which is then used for all subsequent API activities.
# If the token expires, the plugin will request a new token automatically.
# All communication for this plugin is encrypted by SSL/TLS communication.
class LogStash::Inputs::Office365ManagementApi < LogStash::Inputs::Base
config_name "office365_management_api"
# Codec used to decode the incoming data.
# This codec will be used as a fall-back if the content-type
# is not found in the "additional_codecs" hash
default :codec, "plain"
# Fix for broken ruby ADAL
module ADAL
class TokenRequest
module GrantType
JWT_BEARER = 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
end
end
end
# Client ID generated through your custom application in Azure AD
# https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps
config :client_id, :validate => :string, :required => true
# Tenant ID/Directory ID of your Office 365 tenant
# https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/Properties
config :tenant_id, :validate => :string, :required => true
# Your Office 365 tenant domain, ie. yourdomain.onmicrosoft.com
config :tenant_domain, :validate => :string, :required => true
# Resource you are requesting access to. This defaults to https://manage.office.com and shouldn't change unless necessary.
config :resource, :validate => :string, :default => 'https://manage.office.com'
# PFX Private key for your Application Certificate you created
config :private_key, :validate => :path
# Private key password if one was used
config :private_key_password, :validate => :string, :default => nil
# Activity subscriptions you want to monitor
# These can be one or many of:
# Audit.AzureActiveDirectory
# Audit.Exchange
# Audit.Sharepoint
# Audit.General
# DLP.All
config :subscriptions, :validate => :array, :default => ["Audit.AzureActiveDirectory", "Audit.Exchange", "Audit.SharePoint", "Audit.General", "DLP.All"]
public
def register
require "adal"
#logger.info("Starting Office 365 Management API input...")
#host = Socket.gethostname
# ADAL supports four logging options: VERBOSE, INFO, WARN and ERROR.
ADAL::Logging.log_level = ADAL::Logger::VERBOSE
end # def register
def get_token
#logger.info("Generating access token...")
if #private_key_password.nil?
pfx = OpenSSL::PKCS12.new(File.read(#private_key))
else
pfx = OpenSSL::PKCS12.new(File.read(#private_key), #private_key_password)
end
authority = ADAL::Authority.new("login.microsoftonline.com", #tenant_domain)
client_cred = ADAL::ClientAssertionCertificate.new(authority, #client_id, pfx)
result = ADAL::AuthenticationContext
.new("login.microsoftonline.com", #tenant_domain)
.acquire_token_for_client(#resource, client_cred)
case result
when ADAL::SuccessResponse
puts 'Successfully authenticated with client credentials. Received access ' "token: #{result.access_token}."
# Create class variable for reuse of Access Token
#access_token = result.access_token
#http_headers = {
'Authorization' => "Bearer #{#access_token}",
'Content-Type' => 'application/x-www-form-urlencoded'
}
when ADAL::FailureResponse
puts 'Failed to authenticate with client credentials. Received error: ' "#{result.error} and error description: #{result.error_description}."
exit 1
end
end #def get_token
def check_subscription
#logger.info("Checking for proper subscriptions...")
#subscriptions.each do |sub|
sub_uri = URI("https://manage.office.com/api/v1.0/#{#tenant_id}/activity/feed/subscriptions/start?contentType=#{sub}")
sub_http = Net::HTTP.new(sub_uri.host, sub_uri.port)
sub_http.use_ssl = true
sub_resp = http.post(sub_uri.request_uri, data = "", #http_headers)
case sub_resp
when Net::HTTPSuccess
puts "Created subscription to #{sub} in tenant #{#tenant_id}..."
when Net::HTTPUnauthorized
puts "Authentication Error Encountered: #{sub_resp.message}"
when Net::HTTPServerError
puts "Server Error Encountered: #{sub_resp.message}"
else
puts "Unknown Error Encountered: #{sub_resp.message}"
end
end
end #def check_subscription
def run(queue)
# we can abort the loop if stop? becomes true
while !stop?
#event = LogStash::Event.new("message" => #message, "host" => #host)
#decorate(event)
#queue << event
raise 'Error getting token' unless get_token().status == 0
# because the sleep interval can be big, when shutdown happens
# we want to be able to abort the sleep
# Stud.stoppable_sleep will frequently evaluate the given block
# and abort the sleep(#interval) if the return value is true
Stud.stoppable_sleep(#interval) { stop? }
end # loop
end # def run
def stop
# nothing to do in this case so it is not necessary to define stop
# examples of common "stop" tasks:
# * close sockets (unblocking blocking reads/accepts)
# * cleanup temporary files
# * terminate spawned threads
end
end # class LogStash::Inputs::Office365ManagementApi
There appears to be a bug when using empty passwords with logstash.
The code responsible for this comes from the calculate_property method in HttpClient , that treats empty strings as nil:
default = nil if default.is_a?(String) && default.empty? # Blanks are as good as nil
uri_value = nil if uri_value.is_a?(String) && uri_value.empty?
One way to fix this is by upgrading to the latest Logstash, if you are currently using an older version.

Serving files & handling requests with Webrick on same port

Hi I have a need to be able to receive requests from GitLab (body => JSON) as well as serve files on same port. I am trying to use Webrick for this purpose. I can do these separately.
To serve files I do:
server = WEBrick::HTTPServer.new(:Port => 3030, :DocumentRoot => '/')
server.start
To receive and process jSON I do:
server = WEBrick::HTTPServer.new(:Port => 3030, :DocumentRoot => '/')
server.mount_proc '/' do | req, res |
Queue.new(req.body)
end
But I need this functionality combined, is there a way to do this with Webrick?
Yes, this is certainly possible with Webrick or any HTTP server. There will be two different HTTP actions depending on what the users wants to do, 1.) a GET request to serve the files or 2.) a POST request to process some JSON.
Here's a simple example to show you how to do both:
class Server < WEBrick::HTTPServlet::AbstractServlet
def do_GET (request, response)
puts "this is a get request"
end
def do_POST (request, response)
puts "this is a post request who received #{request.body}"
end
end
server = WEBrick::HTTPServer.new(:Port => 3030)
server.mount "/", Server
trap("INT") {
server.shutdown
}
server.start
Once that is running you can test this by doing the following in a separate terminal window:
curl localhost:3030
output:
this is a get request
localhost - - [23/Apr/2015:06:39:20 EDT] "GET / HTTP/1.1" 200 0
- -> /
To test the POST request:
curl -d "{\"json\":\"payload\"}" localhost:3030
output:
this is a post request who received {"json":"payload"}
localhost - - [23/Apr/2015:06:40:07 EDT] "POST / HTTP/1.1" 200 0
- -> /
Since you mentioned the purpose was a light code-base, here's a light, fast script using the Plezi framework...
This will allow for easier testing, I think (but I'm biased). Also, Plezi is faster on my machine then Webrick (although it's a pure ruby framework, no rack or 'c' extensions involved).
require 'plezi'
class MyController
def index
# parsed JSON is acceible via the params Hash i.e. params[:foo]
# raw JSON request is acceible via request[:body]
# returned response can be set by returning a string...
"The request's params (parsed):\n#{params}\n\nThe raw body:\n#{request[:body]}"
end
end
# start to listen and set the root path for serving files.
listen root: './'
# set a catch-all route so that MyController#index is always called.
route '*', MyController
(if you're running the script from the terminal, remember to exit irb using the exit command - this will activate the web server)

Multi-Threading in Ruby

I have a TCPserver that I made in ruby, the server seems to work, I can see that two or more clients can connect and be served by the server, but, they sometime get stuck (as in need to wait for the other client to disconnect or just get unresponsive), usually after the "pass_ok" bit, When connecting only with one client I don't see this issue.
Here is my code:
def self.main_server
begin
server = TCPServer.open(#port)
rescue Exception => e
CoreLogging.syslog_error("Cant start server: #{e}")
end
#main_pid = Process.pid
# Main Loop
Thread.abort_on_exception = true
while true
Thread.fork(server.accept) do |client|
#client = client
sock_domain, remote_port, remote_hostname, remote_ip = #client.peeraddr # Get some info on the incoming connection
CoreLogging.syslog_error("Got new connection from #{#client.peeraddr[3]} Handeled by Thread: #{Thread.current}") # Log incoming connection
#client.puts "Please enter password: " # Password testing (later will be from a config file or DB)
action = #client.gets(4096).chomp # get client password response 'chomp' is super important
if action == #password
# what to do when password is right
pass_ok
Thread.exit
else
# what to do when password is wrong
pass_fail
Thread.exit
end
end
begin
CoreLogging.syslog_error("Thread Ended (SOFT)")
rescue Exception => e
CoreLogging.syslog_error("Thread was killed (HARD)")
end
end
end
I'll leave it here for future reference and hope someone in a close situation will find it useful.
The issue was the global #client variable, which got overwritten every new thread and then inherited to the subclasses inside the thread.
using a local client variable (without the '#') got it to work as supposed.

How to capture POST data from a simple Ruby server

I have a basic Ruby server that I'd like to listen to a specific port, read incoming POST data and do blah...
I have this:
require 'socket' # Get sockets from stdlib
server = TCPServer.open(2000) # Socket to listen on port 2000
loop { # Servers run forever
client = server.accept # Wait for a client to connect
client.puts(Time.now.ctime) # Send the time to the client
client.puts "Closing the connection. Bye!"
client.close # Disconnect from the client
}
How would I go about capturing the POST data?
Thanks for any help.
It's possible to do this without adding much to your server:
require 'socket' # Get sockets from stdlib
server = TCPServer.open(2000) # Socket to listen on port 2000
loop { # Servers run forever
client = server.accept # Wait for a client to connect
method, path = client.gets.split # In this case, method = "POST" and path = "/"
headers = {}
while line = client.gets.split(' ', 2) # Collect HTTP headers
break if line[0] == "" # Blank line means no more headers
headers[line[0].chop] = line[1].strip # Hash headers by type
end
data = client.read(headers["Content-Length"].to_i) # Read the POST data as specified in the header
puts data # Do what you want with the POST data
client.puts(Time.now.ctime) # Send the time to the client
client.puts "Closing the connection. Bye!"
client.close # Disconnect from the client
}
For really simple apps you probably want to write something using Sinatra which is about as basic as you can get.
post('/') do
# Do stuff with post data stored in params
puts params[:example]
end
Then you can stick this in a Rack script, config.ru, and host it easily using any Rack-compliant server.
client.read(length) # length is length of request header content

savon ruby best practices - global client or one per request?

I have Savon working in a Sinatra ruby application. The application will be called frequently, and I don't want to lean on the server too much.
It looks to me that everytime the /test_savon GET is hit, I am going to the server and asking for the wdsl again. I would only need to do that once, it would seem.
Should I make a few clients as ruby globals (one for each wsdl) and use them repeatedly?
Here is my code which works: NTLM auth - talking to a MS DynamicsNav Server
get '/test_savon' do
# create a client for the service
client = Savon.client(wsdl: 'http://somedynamicsnavserver:7047/WS/Page/Salesperson', ntlm: ["username", "password"]) do
convert_request_keys_to :camelcase
end
operations = client.operations
puts "operations are #{operations.to_s}" if operations
puts "checked operations" if operations
# => [:find_user, :list_users]
# call the 'findUser' operation
response = client.call(:read, message: { code: 'salepersonIDhere' })
puts "response is #{response.to_s}" if response
response.body.to_s
# => {:read_result=>{:salesperson=>{:key=>"aKey", :code=>"salepersonIDhere", :name=>"Jim Kirk", :global_code=>"X", :phone_no=>"4407"}, :#xmlns=>"urn:microsoft-dynamics-schemas/page/salesperson"}}
end
I usually don't use a WSDL at all but work without it. That should be much faster because you should have less roundtrips.
A small example:
#!ruby
gem "savon", "~>2.0"
require 'savon'
stock_handle = ARGV[0] || 'OTEX'
client = Savon.client(
endpoint: 'http://www.webservicex.net/stockquote.asmx',
namespace: 'http://www.webserviceX.NET/',
convert_request_keys_to: :camelcase, # :camelcase, :upcase, :none
log: true,
log_level: :debug,
pretty_print_xml: true
)
response = client.call(
:get_quote,
soap_action: 'http://www.webserviceX.NET/GetQuote',
message: { "wsdl:symbol" => stock_handle}
)
print response.to_hash

Resources