Send additional information with Raven.send_event - ruby

I have integrated Sentry in my Ruby On Rails application and I want to send a custom event when a specific case happens, I am able to send the events with
Raven.send_event({:message => 'Custom event'})
How do i send additional information related to the same. I need to send for example, user_id, email_id, login_name and other custom parameters.

You can set user_context to raven using Raven.user_context method
You can write a method to set context in Application Controller and call the same in before action
For example
Raven.user_context(
# a unique ID which represents this user
id: current_user.id, # 1
# the actor's email address, if available
email: current_user.email, # "example#example.org"
# the actor's username, if available
username: current_user.username, # "foo"
# the actor's IP address, if available
ip_address: request.ip # '127.0.0.1'
)
You can write in application controller as
class ApplicationController < ActionController::Base
before_action :set_raven_context, if: proc { Rails.env.production? } //If you want to set only in prod
def set_raven_context
Raven.user_context(
# a unique ID which represents this user
id: current_user.id, # 1
# the actor's email address, if available
email: current_user.email, # "example#example.org"
# the actor's username, if available
username: current_user.username, # "foo"
# the actor's IP address, if available
ip_address: request.ip # '127.0.0.1'
)
#You can also set extra context using `Raven.extra_context`
Raven.extra_context app: url, environment: Rails.env, time: Time.now
end
end

Addition to #opensource-developer answer:
You can find more available extra parameters here => Sentry Usage Page

For anyone facing similar issue, i managed to do it via, under the key extra you can specify any extra keys which you want to send to sentry for further debugging
Raven.capture_message "custom message",
logger: 'logger',
extra: {
time_at: Time.now
},
tags: {
env: Rails.env
}

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.

application and actioncable won't share cookie

I am using devise for authentication, but when I implemented your method I got "An unauthorized connection attempt was rejected"
After hours of searching I found out that:
cookies.signed['user.id']
returns nil. In the following code block.
def find_verified_user
if verified_user = User.find_by(id: cookies.signed['user.id'])
verified_user
else
reject_unauthorized_connection
end
end
I checked and there is definitely a cookie but it does not contain the cookie data set by Devise.
To check if the 'user.id' actually is set I raise it in the view. This, as excepted, return the user id
Signed in as ##{cookies.signed[:username]}.
- raise(cookies.signed['user.id'].inspect)
%br/
%br/
#messages
%br/
%br/
= form_for :message, url: messages_path, remote: true, id: 'messages-form' do |f|
= f.label :body, 'Enter a message:'
%br/
= f.text_field :body
%br/
= f.submit 'Send message'
My question/issue:
It seems like the cookie is not available at the actioncable server.
Is there a way to share the cookie set by Devise with the cable server?
https://github.com/stsc3000/actioncable-chat.git
Check the client-side JavaScript file that connects to your Action Cable server. Some tutorials have you put that in 'app/assets/javascripts/application_cable.coffee' and others in 'app/assets/javascripts/channels/index.coffee' but it looks like this:
#= require cable
#App = {}
App.cable = Cable.createConsumer("ws://cable.example.com:28080")
You need the WebSocket address to point to your Cable server and that address needs to share a cookie namespace with the rest of your app. Most likely yours is pointing at the wrong place, so for example if you're working on this locally you would need to change:
App.cable = Cable.createConsumer("ws://cable.example.com:28080")
to
App.cable = Cable.createConsumer("ws://localhost:28080")
assuming of course that your Cable server is running on port 28080 (specified in the bin/cable executable).
Also make sure to clear your browser cache so the updated file is the one being used by the browser.
Not sure if you got it running by now, but I had the same issue on Rails 5.0.0.beta3. I did not change to the following line:
App.cable = Cable.createConsumer("ws://localhost:3000")
I kept it as it was before
#App ||= {}
App.cable = ActionCable.createConsumer()
But what I did change had to do with the Cookies. No matter what. The cookie for my user_id would not display. So I made a work around. I got the cookie to save the username instead, then I was finally able to see it in the find_verified_user function call.
After the user logs in(Sessions#create), I call a helper function:
sessions_helper.rb
def set_cookie(user)
the_username = user.username.to_s
cookies.permanent.signed[:username] = the_username
end
The new find_verified_user
def find_verified_user
if current_user = User.find_by_username(cookies.signed[:username])
current_user
else
reject_unauthorized_connection
end
end
This may or may not be the best solution, but after hours of confusion and frustration this worked for my situation. I hope this can help someone
You need to configure in config/initializers/session_store.rb
# using cookie store
if Rails.env.production?
# to share across subdomains
Rails.application.config.session_store :cookie_store,
key: '_app_name_session', domain: ".example.com"
else
# to share with any domain
Rails.application.config.session_store :cookie_store,
key: '_app_name_session', domain: :all, tld_length: 2
end
#for redis store
elsif Rails.env.production?
# to share across subdomains
Rails.application.config.session_store :redis_store, {
servers: [
{ host: YourRedisHost, port: YourRedisPort},
],
key: '_app_name_session',
expire_after: 1.day,
domain: '.example.com'
}
else
# to share with any domain
Rails.application.config.session_store :redis_store, {
servers: [
{ host: YourRedisHost, port: YourRedisPort},
],
key: '_app_name_session',
expire_after: 1.day,
domain: :all,
tld_length: 2
}
end
the problem I found is: I have 2 different users logged in. One is logged in at 127.0.0.1, and the other is logged in at localhost.
so when I access my website using 127.0.0.1:3000, but my cable is configured to run on localhost like this:
config.action_cable.url = "ws://localhost:3000/cable"
in config/environments/development.rb
the user logged in at 127.0.0.1 makes cable request to "ws://localhost:3000/cable" (as per configuration), but this way the cookie saved for localhost is sent, even though I am making the request from 127.0.0.1, which is different user(or no user at all).
So the bottom root is actually what Pwnrar points above, cable address configuration and the way you access your website.
So to solve the problem, always access your website using the server address configured for your cable, otherwise cookies get mixed in.

Google Adwords API error: invalid grant

I'm receiving this error trying to authenticate with the Adwords API using a service account and JWT with the Ruby API library.
I am copying the example provided, but it just doesn't seem to work.
/home/michael/.rvm/gems/ruby-2.1.2/gems/signet-0.5.1/lib/signet/oauth_2/client.rb:941:in `fetch_access_token': Authorization failed. Server message: (Signet::AuthorizationError)
{
"error" : "invalid_grant"
}
adwords_api.yml
---
# This is an example configuration file for the AdWords API client library.
# Please fill in the required fields, and copy it over to your home directory.
:authentication:
# Authentication method, methods currently supported: OAUTH2, OAUTH2_JWT.
:method: OAUTH2_JWT
# Auth parameters for OAUTH2_JWT method. See:
# https://developers.google.com/accounts/docs/OAuth2ServiceAccount
:oauth2_issuer: 43242...apps.googleusercontent.com
:oauth2_secret: 'notasecret'
# You can provide path to a file with 'oauth2_keyfile' or the key itself with
# 'oauth2_key' option.
:oauth2_keyfile: /home/.../google-api-key.p12
# To impersonate a user set prn to an email address.
:oauth2_prn: my#email.com
# Other parameters.
:developer_token: ua...w
:client_customer_id: 123-123-1234
:user_agent: test-agent
:service:
# Only production environment is available now, see: http://goo.gl/Plu3o
:environment: PRODUCTION
:connection:
# Enable to request all responses to be compressed.
:enable_gzip: false
# If your proxy connection requires authentication, make sure to include it in
# the URL, e.g.: http://user:password#proxy_hostname:8080
# :proxy: INSERT_PROXY_HERE
:library:
:log_level: INFO
test.rb
#!/usr/bin/env ruby
require 'adwords_api'
def use_oauth2_jwt()
adwords = AdwordsApi::Api.new
adwords.authorize()
campaign_srv = adwords.service(:CampaignService, API_VERSION)
selector = {
:fields => ['Id', 'Name', 'Status'],
:ordering => [
{:field => 'Name', :sort_order => 'ASCENDING'}
]
}
response = campaign_srv.get(selector)
if response and response[:entries]
campaigns = response[:entries]
campaigns.each do |campaign|
puts "Campaign ID %d, name '%s' and status '%s'" %
[campaign[:id], campaign[:name], campaign[:status]]
end
else
puts 'No campaigns were found.'
end
end
if __FILE__ == $0
API_VERSION = :v201409
begin
use_oauth2_jwt()
# HTTP errors.
rescue AdsCommon::Errors::HttpError => e
puts "HTTP Error: %s" % e
# API errors.
rescue AdwordsApi::Errors::ApiException => e
puts "Message: %s" % e.message
puts 'Errors:'
e.errors.each_with_index do |error, index|
puts "\tError [%d]:" % (index + 1)
error.each do |field, value|
puts "\t\t%s: %s" % [field, value]
end
end
end
end
This is going to be difficult to answer definitively as it's authorisation based so the error message is a glorified "not authorised" message.
All I can really do is suggest a few things to check (acknowledging you've probably went through these already):
Your developer token is definately showing as 'Approved'? (you can check this in the client centre - through the setting cog then account then adwords api centre)
You have registered an application through Google Developer Console
You (or the owner of the account you are trying to access) have authorised your application - probably by following this guide and definately seeing one of these things at somepoint:
If you have checked all of these then the only other thing I can suggest is a post to the official forum where they tend to be helpful and often take authorisation issues 'offline' to have a look at the actual soap requests etc. (I have found this much quicker and easier than trying to wade through the levels of AdWords 'support')
Good luck!
After several more hours of fiddling, I finally got it working by setting the oauth2_prn to the primary email on the MCC and Google Apps for Business account.

Validating Jenkins plugin forms with Ruby

I'm developing a Jenkins plugin in Ruby. You're supposed to be able to configure every node that connects to the server so that an email is sent to a specified address when the node loses its connection to the master. EmailNodeProperty adds a field to enter an email address:
#
# Save an email property for every node
#
class EmailNodeProperty < Jenkins::Slaves::NodeProperty
require 'java'
import 'hudson.util.FormValidation'
display_name "Email notification"
attr_accessor :email
def initialize(attrs = {})
#email = attrs['email']
end
def doCheckEmail value
puts " ENP.doCheckEmail:#{value}"
end
end
When you configure a node, there's a field named email where you can enter an email address. I want this field to be validated when you enter an address.
When you save the configuration, an EmailNodeProperty is created whence (that's right) you can access the email address.
MyComputerListener's offline gets called when a node loses its connection:
class MyComputerListener
include Jenkins::Slaves::ComputerListener
include Jenkins::Plugin::Proxy
def online(computer, listener)
end
def offline(computer)
#Do nothing when the Master shuts down
if computer.to_s.match('Master') == nil
list = computer.native.getNode().getNodeProperties()
proxy = list.find {"EmailNodeProperty"}
if proxy.is_a?(Jenkins::Plugin::Proxy)
rubyObject = proxy.getTarget()
email = rubyObject.email #<= Accesses the email from EmailNodeProperty
[...]
end
end
end
end
MyComputerListener finds the email address and sends an email.
Does anybody know if it is possible to validate the form in Ruby? According to the Jenkins wiki, this is what's supposed to be implemented (FIELD is supposed to be exchanged for the field name, so I guess it should be doCheckEmail):
public FormValidation doCheckFIELD(#QueryParameter String value) {
if(looksOk(value))
return FormValidation.ok();
else
return FormValidation.error("There's a problem here");
}
How would you do this in Ruby? Where should the method be implemented? In EmailNodeProperty or in MyComputerListener? How do you handle the QueryParameter? The # would make it an intstance variable in Ruby. (What is a Queryparameter?)
Any help would be much appreciated!
/Jonatan
This simply doesn't exist today, and we badly need to add it. This has been brought up a couple of times already in Thursday morning's hack session, so it's high on the TODO list. But as of ruby-runtime plugin 0.10, this just isn't possible. Sorry to let you down.

How to check for user credentials using active directory and a ruby script

I'm trying write a Ruby script that checks if user credentials are valid using an active directory server. Here's what I've tried so far:
require 'rubygems'
require 'net-ldap'
host = '10.4.1.6'
port = 389
username = 'username'
password = 'password'
ldap = Net::LDAP.new
ldap.host = host
ldap.port = port
ldap.auth "CN=#{username},CN=Users,DC=companyname,DC=ad", password
if ldap.bind
puts 'YES!'
puts ldap.get_operation_result.message
else
puts 'NO :-('
puts ldap.get_operation_result.message
end
If I enter a non existing username and an empty string as a password, the bind operation succeeds. If I enter a valid username and a valid/invalid/empty password, the bind operation fails with error message 'Invalid Credentials'.
I've looked at other threads and read the net-ldap documentation but I can't figure out what I'm doing wrong.
Can someone give me some ideas on how to achieve this?
Thanks in advance for any replies :-)
Edit:
As #StuartEllis suggested, the problem was with the user identifier. To figure out the correct DN, I used the following script (taken from the net-ldap documentation):
ldap.auth "CN='adminUser',CN=Users,DC=companyname,DC=ad", 'adminUserPwd'
ldap.bind
treebase = "DC=companyname,DC=ad"
filter = Net::LDAP::Filter.eq( "mail", "username#companyname.com" )
attrs = ["mail", "cn", "sn","objectclass"]
ldap.search( :base => treebase, :filter => filter, :attributes => attrs, :return_result => false ) do |entry|
puts entry._dump 0
end
I then retried using my original script (above) with the obtained DN and voila!
I would guess that your LDAP account details aren't correct, but your LDAP server accepts anonymous binds, which is why it works when you don't specify a valid username and password. LDAP user identifiers are very fiddly, so I'd suggest double-checking the whole thing, including the case of the parts.
Here is sample code I use with the net-ldap gem to verify user logins from the ActiveDirectory server at my work:
def name_for_login( email, password )
email = email[/\A\w+/].downcase # Throw out the domain, if it was there
email << "#mycompany.com" # I only check people in my company
ldap = Net::LDAP.new(
host: 'ldap.mycompany.com', # Thankfully this is a standard name
auth: { method: :simple, email: email, password:password }
)
if ldap.bind
# Yay, the login credentials were valid!
# Get the user's full name and return it
ldap.search(
base: "OU=Users,OU=Accounts,DC=mycompany,DC=com",
filter: Net::LDAP::Filter.eq( "mail", email ),
attributes: %w[ displayName ],
return_result:true
).first.displayName.first
end
end

Resources