Looks like HTTPBuilder's behavior goes wrong when the server / api endpoint does not give a proper response. Mailgun's API does not respond 401, but instead gives something else. (Correct me if I'm wrong!)
The fact that there are no "Authentication required" headers sent, HTTPBuilder will not even try to authenticate.
I think I got the problem solved by the help of this thread: http://groovy.329449.n5.nabble.com/HTTPBuilder-Strange-behaviour-of-auth-basic-td344479.html
They suggest, that writing the authentication header manually will force the authentication each time. Needed to modify that a bit so it looks like this:
def user = 'tom'
def pass = 'secret'
def http = new HTTPBuilder('http://myhost.com')
http.headers[ 'Authorization' ] = "Basic " + "$user:$pass".getBytes('iso-8859-1').encodeBase64()
Seems to work now. Any better ideas? This looks like and perhaps is a hack.
Try to use something like:
def authSite = new HTTPBuilder( 'https://some-protected-site.com/' )
authSite.auth.basic 'myUserName', 'myPassword'
Read more at: http://groovy.codehaus.org/modules/http-builder/doc/auth.html
Related
I'm new to Rails and I'm using rest-client to make outbound requests. It's easy to get successful results for a simple call like this:
#obj = RestClient.get 'https://jsonplaceholder.typicode.com/posts/1'
I need to hit a real endpoint and send a header with a jwt token (using ruby-jwt). According to the JWT readme, the content of the header should look like this:
Authorization: Bearer <token>
So I have some code to use a secret to make that token (and I confirmed the resulting token is valid) and put it into a headers variable, but I'm unsure about the syntax on that headers line, and whether it's right to use strings:
def build_headers (secret)
jwt_token = make_signed_JWT_token(secret)
headers = ("Authorization: Bearer "+ jwt_token)
return headers
end
Running it produces a 'headers' value like this:
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1MzIxMTc3MjF9.dP2k1oPwjna5HdrnFeSqiVfR0Fz6J1ZlupfXMsPtFKw
I include that in my rest-client invocation like so:
#obj = RestClient.get(path, headers)
I'm no longer get a 401 Unauthorized error, but no celebrating yet; we appear to jump out of the code block at that line, with this exception:
undefined method `delete_if' for #<String:0x007f9e4de410b8>
Looking in the rest-client code, there is exactly one block that uses delete_if, whose purpose is to find and extract/remove "params" key if the value is a Hash/ParamsArray:
headers.delete_if do |key, value|
if key.to_s.downcase == 'params' &&
(value.is_a?(Hash) || value.is_a?(RestClient::ParamsArray))
if url_params
raise ArgumentError.new("Multiple 'params' options passed")
end
url_params = value
true
else
false
end
So my error suggests that it found something in this forbidden format and is trying to delete it, but that delete method isn't defined to act on a String. My best hunch is that there's something the matter with that headers item I created, but reading around I'm not finding more clues. Has anyone seen this before, or know if my jwt/header should be different?
You are passing a string where a hash is expected. Try this:
def build_headers (secret)
jwt_token = make_signed_JWT_token(secret)
headers = { authorization: "Bearer "+ jwt_token }
return headers
end
I'm building Slack integration for my Ruby on Rails application and I'm trying to get an access_token from the Slack API for my Slack App when a user clicks the Add to Slack button.
From Postman, I can successfully post the following:
https://slack.com/api/oauth.access?client_id=idgoes.here&client_secret=secretgoeshere&code=12345&pretty=1
However, within Rails I always get a response with invalid_client_id, regardless of the way I call the API. I have checked my ID is correct (a lot) and tried regenerating it, but I don't think that is the issue due to the postman success.
Within my get_oauth_access_token method I have tried the following implementations:
1.
rc = JSON.parse(HTTP.post('https://slack.com/api/oauth.access',
params: {
client_id: 'idgoes.here',
client_secret: 'secretgoeshere',
code: '12345'
}))
2.
response = Excon.post('https://slack.com/api/oauth.access',
headers: { 'Content-Type' => 'application/json; charset=utf-8' },
user: client_id, password: client_secret,
body: oauth_request_body.to_json)
Any implementation I try always ends up getting a invalid_client_id response.
I'm aware it may be something to do with environment config, but I'm not sure what would be helpful to debug, so please let me know what other information I can share. I'm running on localhost.
Update:
I just found out that many (maybe all) of the Slack APIs do not accept a JSON format body (which seems crazy seeing as they send a response in JSON.
Make sure to use x-www-form-urlencoded format body on your request or it will not work properly.
"Content-Type" => "application/x-www-form-urlencoded"
I use oauth2 gem to authorize. So I was able to get this to work by reading the slack documentation and using oauth2 in my controller:
class OauthController < ApplicationController
def authorize
options = {
site: 'https://slack.com/oauth/authorize'
}
client ||= OAuth2::Client.new(
'client-id',
'client-secret',
options
)
params = {
scope: 'incoming-webhook, commands',
redirect_uri: 'https://localhost:3000/oauth/callback'
}
redirect_to client.auth_code.authorize_url(params)
end
def authorize_callback
puts params["code"]
redirect_to root_url
end
end
Routes file:
get '/authorize', to: 'oauth#authorize'
get '/oauth/callback', to: 'oauth#authorize_callback'
Don't forget to set your callback url at Oauth settings on api.slack.com, I used localhost for testing purposes as you can see.
I am relatively new to writing code against REST APIs. It is possible I am analyzing the wrong problem here, so feel free to give big picture advice. The other twist is that the API I want to use is not yet configured, so I can't test.
I need to write some Rails code to create webhooks on the Jive API. Jive's docs show how to register the webservice via a curl request. I want to build the code as an admin function of my app in case we need to recreate the webhook for any reason.
Here are the Jive Docs.
Based on this guide, I'm thinking I need something like (I expect this example to sent a POST request to "sample.jiveon.com/api/core/v3/webhooks"):
#host = "sample.jiveon.com/api/core/v3"
#port = "443"
#post_ws = "/webhooks"
#payload ={
"events" => "document",
"callback" => "my_app/jive_listener",
"object" => "my/jive/space"
}.to_json
def post
req = Net::HTTP::Post.new(#post_ws, initheader = {'Content-Type' =>'application/json'})
req['Authorization'] = "Bearer my_key"
req.body = #payload
response = Net::HTTP.new(#host, #port).start {|http| http.request(req) }
end
end
Thanks.
It would be better to use gem like 'rest-client(https://github.com/rest-client/rest-client)'
Above gem does the many stuff, which you might be doing manually using bare ruby library. It depends on need of yours.
I'm trying to use Microsoft's Translator API in my Rails app. Unfortunately and mostly unexpected, the server answers always with an internal server error. I also tried it manually with Poster[1] and I get the same results.
In more detail, what am I doing? I'm creating an XML string which goes into the body of the request. I used the C# Example of the API documentation. Well, and then I'm just invoking the RESTservice.
My code looks like this:
xmlns1 = "http://schemas.datacontract.org/2004/07/Microsoft.MT.Web.Service.V2"
xmlns2 = "http://schemas.microsoft.com/2003/10/Serialization/Arrays"
xml_builder = Nokogiri::XML::Builder.new(:encoding => 'UTF-8') do |xml|
xml.TranslateArrayRequest("xmlns:ms" => xmlns1, "xmlns:arr" => xmlns2) {
xml.AppId token #using temporary token instead of appId
xml.From source
xml.To target
xml.Options {
xml["ms"].ContentType {
xml.text "text/html"
}
}
xml.Texts {
translate.each do |key,val|
xml["arr"].string {
xml.text CGI::unescape(val)
}
end
}
}
end
headers = {
'Content-Type' => 'text/xml'
}
uri = URI.parse(##msTranslatorBase + "/TranslateArray" + "?appId=" + token)
req = Net::HTTP::Post.new(uri.path, headers)
req.body = xml_builder.to_xml
response = Net::HTTP.start(uri.host, uri.port) { |http| http.request(req) }
# [...]
The xml_builder produces something like the following XML. Differently to the example from the API page, I'm defining two namespaces instead of referencing them on the certain tags (mainly because I wanted to reduces the overhead) -- but this doesn't seem to be a problem, when I do it like the docu-example I also get an internal server error.
<?xml version="1.0" encoding="UTF-8"?>
<TranslateArrayRequest xmlns:ms="http://schemas.datacontract.org/2004/07/Microsoft.MT.Web.Service.V2" xmlns:arr="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<AppId>TX83NVx0MmIxxCzHjPwo2_HgYN7lmWIBqyjruYm7YzCpwnkZL5wtS5oucxqlEFKw9</AppId>
<From>de</From>
<To>en</To>
<Options>
<ms:ContentType>text/html</ms:ContentType>
</Options>
<Texts>
<arr:string>Bitte übersetze diesen Text.</arr:string>
<arr:string>Das hier muss auch noch übersetzt werden.</arr:string>
</Texts>
</TranslateArrayRequest>
Every time I request the service it answers with
#<Net::HTTPInternalServerError 500 The server encountered an error processing the request. Please see the server logs for more details.>
... except I do some unspecified things, like using GET instead of POST, then it answers with something like "method not allowed".
I thought it might be something wrong with the XML stuff, because I can request an AppIdToken and invoke the Translate method without problems. But to me, the XML looks just fine. The documentation states that there is a schema for the expected XML:
The request body is a xml string generated according to the schema specified at http:// api.microsofttranslator.com/v2/Http.svc/help
Unfortunately, I cannot find anything on that.
So now my question(s): Am I doing something wrong? Maybe someone experienced similar situations and can report on solutions or work-arounds?
[1] Poster FF plugin > addons.mozilla.org/en-US/firefox/addon/poster/
Well, after lot's of trial-and-error I think I made it. So in case someone has similar problems, here is how I fixed this:
Apparently, the API is kind of fussy with the incoming XML. But since there is no schema (or at least I couldn't find the one specified in the documentation) it's kind of hard to do it the right way: the ordering of the tags is crucial!
<TranslateArrayRequest>
<AppId/>
<From/>
<Options />
<Texts/>
<To/>
</TranslateArrayRequest>
When the XML has this ordering it works. Otherwise you'll only see the useless internal server error response. Furthermore, I read a couple of times that the API also breaks if the XML contains improper UTF-8. One can force untrusted UTF-8 (e.g. coming from a user form) this way:
ic = Iconv.new('UTF-8//IGNORE', 'UTF-8')
valid_string = ic.iconv(untrusted_string + ' ')[0..-2]
A client of mine has asked me to integrate a 3rd party API into their Rails app. The only problem is that the API uses SOAP. Ruby has basically dropped SOAP in favor of REST. They provide a Java adapter that apparently works with the Java-Ruby bridge, but we'd like to keep it all in Ruby, if possible. I looked into soap4r, but it seems to have a slightly bad reputation.
So what's the best way to integrate SOAP calls into a Rails app?
I built Savon to make interacting with SOAP webservices via Ruby as easy as possible.
I'd recommend you check it out.
We used the built in soap/wsdlDriver class, which is actually SOAP4R.
It's dog slow, but really simple. The SOAP4R that you get from gems/etc is just an updated version of the same thing.
Example code:
require 'soap/wsdlDriver'
client = SOAP::WSDLDriverFactory.new( 'http://example.com/service.wsdl' ).create_rpc_driver
result = client.doStuff();
That's about it
We switched from Handsoap to Savon.
Here is a series of blog posts comparing the two client libraries.
I also recommend Savon. I spent too many hours trying to deal with Soap4R, without results. Big lack of functionality, no doc.
Savon is the answer for me.
Try SOAP4R
SOAP4R
Getting Started with SOAP4R
And I just heard about this on the Rails Envy Podcast (ep 31):
WS-Deathstar SOAP walkthrough
Just got my stuff working within 3 hours using Savon.
The Getting Started documentation on Savon's homepage was really easy to follow - and actually matched what I was seeing (not always the case)
Kent Sibilev from Datanoise had also ported the Rails ActionWebService library to Rails 2.1 (and above).
This allows you to expose your own Ruby-based SOAP services.
He even has a scaffold/test mode which allows you to test your services using a browser.
I have used HTTP call like below to call a SOAP method,
require 'net/http'
class MyHelper
def initialize(server, port, username, password)
#server = server
#port = port
#username = username
#password = password
puts "Initialised My Helper using #{#server}:#{#port} username=#{#username}"
end
def post_job(job_name)
puts "Posting job #{job_name} to update order service"
job_xml ="<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:ns=\"http://test.com/Test/CreateUpdateOrders/1.0\">
<soapenv:Header/>
<soapenv:Body>
<ns:CreateTestUpdateOrdersReq>
<ContractGroup>ITE2</ContractGroup>
<ProductID>topo</ProductID>
<PublicationReference>#{job_name}</PublicationReference>
</ns:CreateTestUpdateOrdersReq>
</soapenv:Body>
</soapenv:Envelope>"
#http = Net::HTTP.new(#server, #port)
puts "server: " + #server + "port : " + #port
request = Net::HTTP::Post.new(('/XISOAPAdapter/MessageServlet?/Test/CreateUpdateOrders/1.0'), initheader = {'Content-Type' => 'text/xml'})
request.basic_auth(#username, #password)
request.body = job_xml
response = #http.request(request)
puts "request was made to server " + #server
validate_response(response, "post_job_to_pega_updateorder job", '200')
end
private
def validate_response(response, operation, required_code)
if response.code != required_code
raise "#{operation} operation failed. Response was [#{response.inspect} #{response.to_hash.inspect} #{response.body}]"
end
end
end
/*
test = MyHelper.new("mysvr.test.test.com","8102","myusername","mypassword")
test.post_job("test_201601281419")
*/
Hope it helps. Cheers.
I have used SOAP in Ruby when i've had to make a fake SOAP server for my acceptance tests. I don't know if this was the best way to approach the problem, but it worked for me.
I have used Sinatra gem (I wrote about creating mocking endpoints with Sinatra here) for server and also Nokogiri for XML stuff (SOAP is working with XML).
So, for the beginning I have create two files (e.g. config.rb and responses.rb) in which I have put the predefined answers that SOAP server will return.
In config.rb I have put the WSDL file, but as a string.
##wsdl = '<wsdl:definitions name="StockQuote"
targetNamespace="http://example.com/stockquote.wsdl"
xmlns:tns="http://example.com/stockquote.wsdl"
xmlns:xsd1="http://example.com/stockquote.xsd"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns="http://schemas.xmlsoap.org/wsdl/">
.......
</wsdl:definitions>'
In responses.rb I have put samples for responses that SOAP server will return for different scenarios.
##login_failure = "<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Body>
<LoginResponse xmlns="http://tempuri.org/">
<LoginResult xmlns:a="http://schemas.datacontract.org/2004/07/WEBMethodsObjects" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<a:Error>Invalid username and password</a:Error>
<a:ObjectInformation i:nil="true"/>
<a:Response>false</a:Response>
</LoginResult>
</LoginResponse>
</s:Body>
</s:Envelope>"
So now let me show you how I have actually created the server.
require 'sinatra'
require 'json'
require 'nokogiri'
require_relative 'config/config.rb'
require_relative 'config/responses.rb'
after do
# cors
headers({
"Access-Control-Allow-Origin" => "*",
"Access-Control-Allow-Methods" => "POST",
"Access-Control-Allow-Headers" => "content-type",
})
# json
content_type :json
end
#when accessing the /HaWebMethods route the server will return either the WSDL file, either and XSD (I don't know exactly how to explain this but it is a WSDL dependency)
get "/HAWebMethods/" do
case request.query_string
when 'xsd=xsd0'
status 200
body = ##xsd0
when 'wsdl'
status 200
body = ##wsdl
end
end
post '/HAWebMethods/soap' do
request_payload = request.body.read
request_payload = Nokogiri::XML request_payload
request_payload.remove_namespaces!
if request_payload.css('Body').text != ''
if request_payload.css('Login').text != ''
if request_payload.css('email').text == some username && request_payload.css('password').text == some password
status 200
body = ##login_success
else
status 200
body = ##login_failure
end
end
end
end
I hope you'll find this helpful!
I was having the same issue, switched to Savon and then just tested it on an open WSDL (I used http://www.webservicex.net/geoipservice.asmx?WSDL) and so far so good!
https://github.com/savonrb/savon