Rails Savon SOAP Authentication - ruby-on-rails-3

I am developing on Rails 3 and using Savon(savonrb.com)
I need to connect to Web Services via SOAP
Web Services require authentication
I can connect but can't get authonticated...
I used SOAP UI for testing & all works fine.
Here how my Rails code looks like:
client = Savon::Client.new do
wsdl.document = "http://services.blahblah.com/Service.asmx?WSDL"
end
client.wsse.credentials "username", "password", :digest
if response.success?
#soap_status = 'Connected'
#data = response.to_array(:get_brochure_response, :error_message)
end
I get response but :error_message returns Authentication failed...
Here is how it looks in SOAP UI
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:ser="http://services.blahblah.com/service">
<soap:Header>
<ser:ICEAuthHeader>
<!--Optional:-->
<ser:Username>username</ser:Username>
<!--Optional:-->
<ser:Password>password</ser:Password>
</ser:ICEAuthHeader>
</soap:Header>
<soap:Body>
<ser:GetBrochure>
<!--Optional:-->
<ser:MappedID>123</ser:MappedID>
</ser:GetBrochure>
</soap:Body>
</soap:Envelope>

Are the username and password sent with the SOAP request in the headers? If so, you could go about doing this when initializing the Savon client (see cannot set SOAP header parameters on savon call):
client = Savon.client(
wsdl: SOAP_WSDL,
endpoint: SOAP_URL,
soap_header: { :username => username, :password => password }
)
Incidentally, if only certain calls need authentication, you can make calls like so:
response = client.call(
:soap_call,
:soap_header => { :username => username, :password => password },
message: {message}
)

You could try this to authenticate using basic auth:
client = Savon::Client.new do
wsdl.document = "http://services.blahblah.com/Service.asmx?WSDL"
http.auth.basic "username", "password"
end

client = Savon.client( wsdl: 'http://service.example.com?wsdl',soap_header: { "AuthenticateRequest" => {"apiKey" => "****** your api *********"}}, pretty_print_xml: true)
client.operations
response = client.call(:function)
here "apiKey" is my prams to authenticate api, your can be different

Related

How to send a XML API request in cypress (SOAP API)

I need to send XML requests in cypress but I don't know how to do that. Generally, I worked with cy.request with rest API's. Is it possible to send XML requests in cypress?
How can I do that? Any help would be great!! An example would be lovely!
I left an example request that I use in postman without a problem. But to automate my test I need to send that request in cypress.
(POST request)
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<MT_ServiceOrderInitiaionandMgmt xmlns:prx="***"
xmlns="***">
<VKONTO>***</VKONTO>
<NAME>***</NAME>
<PORTION>***</PORTION>
<AKLASSE/>
<DATUM>***</DATUM>
<Contact>
<RELTYP/>
<NAME>***</NAME>
<TEL_NUMBER>***</TEL_NUMBER>
<PHTYPE>***</PHTYPE>
<SMTP_ADDR>***</SMTP_ADDR>
</Contact>
<LifeSupportIndicator/>
<QMNUM>***</QMNUM>
<QMART>***</QMART>
<MNCOD>***</MNCOD>
<QMTXT>***</QMTXT>
<NOTST>***</NOTST>
<ServiceAddress>
<STREET>***</STREET>
<CITY1>***</CITY1>
<REGIO>***</REGIO>
<LOCATION>***</LOCATION>
<POST_CODE1>***</POST_CODE1>
<HAUS_NUM2/>
</ServiceAddress>
<ABLEINH>***</ABLEINH>
<CO_ASTTX>***</CO_ASTTX>
<STRMN/>
<STRUR>***</STRUR>
<LTRMN/>
<LTRUR>***</LTRUR>
<ERDAT>***</ERDAT>
<MZEIT>***</MZEIT>
<ERNAM>***</ERNAM>
<AEDAT/>
<AEZEIT>***</AEZEIT>
<AENAM/>
<PPSID>***</PPSID>
<ExternalReference>
<ID></ID>
<SYSNAME>***</SYSNAME>
<TYPE>***</TYPE>
</ExternalReference>
<REFNUM/>
<PRIOK/>
<LONT_TXT>***</LONT_TXT>
<PREMS>***</PREMS>
<Meters>
<TPLNR>***</TPLNR>
<VBSARTTEXT>***</VBSARTTEXT>
<TERMSCHL/>
<DLNOT>***</DLNOT>
<STORT>***</STORT>
<KTEXT>***</KTEXT>
<EINBDAT>***</EINBDAT>
<ISPRIM>***</ISPRIM>
<SERNR>***</SERNR>
<MET_SERNR></MET_SERNR>
<HERST>***</HERST>
<TYPBZ/>
<PREISKLA>***</PREISKLA>
<EQKTX>***</EQKTX>
<DSTAT>***</DSTAT>
<MEINS>***</MEINS>
<STANZVORE>***</STANZVORE>
<ISTABLART>***</ISTABLART>
<V_ZWSTAND></V_ZWSTAND>
<E_ZWSTANDO>***</E_ZWSTANDO>
<E_ZWSTANDU>***</E_ZWSTANDU>
<E_VERBERW>***</E_VERBERW>
<ADAT>***</ADAT>
<ATIM>***</ATIM>
<MTU_TPLNR/>
<MTUSN>***</MTUSN> >
<MTU_EQKTX/>
<MTU_EINBDAT>***11</MTU_EINBDAT>
<MTU_SERNR></MTU_SERNR>
<PORT>***</PORT>
</Meters>
</MT_ServiceOrderInitiaionandMgmt>
</soap:Body>
</soap:Envelope>
there are no big differences between standard rest API requests and XML Soap API requests.
cy.request({
method: "POST",
url: "Request URL",
headers: {
"Content-Type": "text/xml;charset=UTF-8",
authorization: "Bearer " + "auth token",
SOAPAction: "serviceOrder",
},
//you have to write your XML body here as a string. I leave a small part
//of my API's body to be an example if someone needs it in the future.
body: '<?xml version="1.0" encoding="UTF-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><MT_ServiceOrderInitiaionandMgmt xmlns:prx="***" xmlns="***"><VKONTO>5883000</VKONTO><NAME>cypress</NAME><PORTION>MBC87<MTU_EINBDAT>2002-05-11</MTU_EINBDAT><MTU_SERNR></MTU_SERNR><PORT>1</PORT></Meters></MT_ServiceOrderInitiaionandMgmt></soap:Body></soap:Envelope>',
})
.then((res) => {
//Do something with the response.
});

Devise Token Auth / Angular2-Token, update password, Completed 401 Unauthorized

I'm having troubles restoring password with devise_token_auth. and Angular2-Token. I'm successfully receiving the email with the link to update my password. But I'm getting an 401 Unauthorized response when submiting the new password.
Front end. I'm getting the token from the URL with urlParams.get('token')
onPasswordUpdate() {
let token = this.urlParams.get('token');
var obj = Object.assign(this._updatePasswordData, { reset_password_token: token })
this._tokenService.patch('auth/password/', obj ).subscribe(
res => res,
error => error
);
}
Back end response.
Started PATCH "/api/auth/password/" for 127.0.0.1 at 2016-12-01 21:17:48 +0100
Processing by DeviseTokenAuth::PasswordsController#update as JSON
Parameters: {"reset_password_token"=>"[FILTERED]", "password"=>"[FILTERED]", "password_confirmation"=>"[FILTERED]"}
Completed 401 Unauthorized in 1ms (Views: 0.4ms | ActiveRecord: 0.0ms)
In the link of the email I get the following token : reset_password_token=HneZDoKTMCLF3_SLfnxy
When I visit the link, the user record gets updated with the following attributes :
reset_password_token: "aa3cba76c7b1d8f78cde6856f43e1cce57f5fc8e5301842733de677eff909bc1"
tokens: {}
Then in the browser URL I get the following token=agejaip2SqOp9nvwE1GAHQ&uid
And then the user record get updated with the following attribues :
...
reset_password_token: "HneZDoKTMCLF3_SLfnxy",
tokens: {"pv9i1BDTM29ezep0KSPzpA"=>{"token"=>"$2a$10$cS9gbe9UBICcgphZHRAENOMS6NlEe0Em1cNufY3LSRTPE.hRMabvi", "expiry"=>1481834221}}
...
It seems to me that the token I get back in URL is not correct.
Those anyone have an idea ?
Sorry It's a bit hard to explain.
Many thanks.
rails (4.2.4)
devise_token_auth (0.1.34)
devise (= 3.5.1)
angular2-token: 0.2.0-beta.1
I faced similar challenges recently, and this was how I solved it.
Expose the 'access-token', 'expiry', 'token-type', 'uid', 'client' for your backend. Check here and here
config.middleware.use Rack::Cors do
allow do
origins '*'
resource '*',
:headers => :any,
:expose => ['access-token', 'expiry', 'token-type', 'uid', 'client'],
:methods => => [:get, :post, :options, :delete, :put, :patch]
end
end
Set your redirect_url of path: /password, method: POST. Check info here
We need to modify the reset_password_instructions.html.erb to point it to the api GET /auth/password/edit. More information provided here.
E.g. if your API is under the api namespaces:
<%= link_to 'Change my password', edit_api_user_password_url(reset_password_token: #token, config: message['client-config'].to_s, redirect_url: message['redirect-url'].to_s) %>

Rails/Devise/SAML Metadata Incorrect (not working with PingFederate)

Let me preface this question by saying that I'm new to SAML and barely understand how it works.
The Setup
I'm using the devise_saml_authenticatable gem with a Rails 4 app to achieve SSO. The Rails app acts as the service provider (SP). To test my setup, I created a OneLogin developer account and set up a SAML Test Connector (IdP w/attr w/ sign response) using the following attributes:
Configuration Tab
Audience: mysubdomain.onelogin.com
Recipient: http://mysubdomain.myapp.local:3000/saml/auth
ACS (Consumer) URL Validator: ^http://mysubdomain.myapp.local:3000/saml/auth$
ACS (Consumer) URL: http://mysubdomain.myapp.local:3000/saml/auth
Single Logout URL: http://mysubdomain.myapp.local:3000/saml/idp_sign_out
SSO Tab
Issuer URL: https://app.onelogin.com/saml/metadata/589819
SAML 2.0 Endpoint (HTTP): https://mysubdomain.onelogin.com/trust/saml2/http-post/sso/589819
SLO Endpoint (HTTP): https://mysubdomain.onelogin.com/trust/saml2/http-redirect/slo/589819
SAML Signature Algorithm: SHA-1
SHA Fingerprint: 60:9D:18:56:B9:80:D4:25:63:C1:CC:57:6D:B9:06:7C:78:BB:2C:F1
X.509 Certificate:
-----BEGIN CERTIFICATE-----
MIIEFzCCAv+gAwIBAgIUQYRVa1MQpUh0gJaznmXSF/SPqnowDQYJKoZIhvcNAQEF
BQAwWDELMAkGA1UEBhMCVVMxETAPBgNVBAoMCEZpcm1QbGF5MRUwEwYDVQQLDAxP
bmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgOTI1MzEwHhcN
MTYwOTIxMTU0NzQwWhcNMjEwOTIyMTU0NzQwWjBYMQswCQYDVQQGEwJVUzERMA8G
A1UECgwIRmlybVBsYXkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW
T25lTG9naW4gQWNjb3VudCA5MjUzMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBALGVgocBj0ciHM3uKlWIcofPhOtzfJw1XpAdNynAvPtbCl7WE5+sLBoQ
ZF+oZ7Dl+wRW6DHMJCl9DdKcOaQA6/gr5bwt78IzZ8hWMoKQEPih+E0km6rKLYA8
M52vxtJxGs8Iqx60QvPEePQFMOA+xg73OExfM7W5LnXwNz/Pxgsr3lBif5oCC76j
SaTCFroV+TSjfOaYMW/lZrsS79KRIzA9I5XwUBe3bC8bsfQmZXgddCrkQUNSGGaS
7/jtFUlQ94+lAL+l3yoAiNAE6+mt48qqmyLfkKibXvnZ8dwuO272wpY4fEM+vFRy
pYrTajqvhY3hYIq8dLw3ominE5VECl8CAwEAAaOB2DCB1TAMBgNVHRMBAf8EAjAA
MB0GA1UdDgQWBBSxiuvTPxwOhh2pupID+tuyKCeceTCBlQYDVR0jBIGNMIGKgBSx
iuvTPxwOhh2pupID+tuyKCeceaFcpFowWDELMAkGA1UEBhMCVVMxETAPBgNVBAoM
CEZpcm1QbGF5MRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxv
Z2luIEFjY291bnQgOTI1MzGCFEGEVWtTEKVIdICWs55l0hf0j6p6MA4GA1UdDwEB
/wQEAwIHgDANBgkqhkiG9w0BAQUFAAOCAQEAYBe+5d3zpLZ7fcf3l3rXYeIxcpN+
9D2YZCbxsrBhY2Am4YE9nN+RaJXeDqeRBNtpayCZVxfHnXexRo1n7wxwTmosiydi
9yE7SY2xZf+3feQreF25atnn4tzVhxYONaX1njZMIt/TNa7A9aeDfHSD+vwSuYYB
hGxKT6HOkEAEBiXCZ/FcVNiB0D8bRwQhiJ3BTzXDfqHrmq8QYdn3Ejlqo62vMl6W
XeMXUoyv6cUc64Ap6E+XtEQI1E8YB5R8GtTs3Y1Oa2dD6yWyCyVJ20+Hi7IWAqXC
EfqstqXB7FoQ2rAt39cepnu1SOarvEYDMwYIaVNF3hoyodBybJJsAwAnCQ==
-----END CERTIFICATE-----
In my devise.rb I have the following configuration:
config.saml_create_user = false
config.saml_update_user = true
config.saml_default_user_key = :email
config.saml_session_index_key = :session_index
config.saml_use_subject = true
config.idp_settings_adapter = IdPSettingsAdapter
config.idp_entity_id_reader = DeviseSamlAuthenticatable::DefaultIdpEntityIdReader
Here is my IdPSettingsAdapter:
class IdPSettingsAdapter
def self.settings(idp_entity_id)
company = Company.find_by(idp_entity_id: idp_entity_id)
if company.present?
{
assertion_consumer_service_url: company.assertion_consumer_service_url,
assertion_consumer_service_binding: company.assertion_consumer_service_binding,
name_identifier_format: company.name_identifier_format,
issuer: company.issuer,
idp_entity_id: company.idp_entity_id,
authn_context: company.authn_context,
idp_slo_target_url: company.idp_slo_target_url,
idp_sso_target_url: company.idp_sso_target_url,
idp_cert_fingerprint: company.idp_cert_fingerprint
}
else
{}
end
end
end
Note that my user model Contact belongs_to Company, and that the SSO settings are stored in the Company model.
Here are my saml routes:
devise_for :contacts, skip: :saml_authenticatable, controllers: {
registrations: "registrations",
sessions: "sessions",
passwords: "passwords",
confirmations: "confirmations"
}
devise_scope :contact do
get '/sign_in' => 'sessions#new'
get '/sign_out' => 'sessions#destroy'
# SSO Routes
get 'saml/sign_in' => 'saml_sessions#new', as: :new_user_sso_session
post 'saml/auth' => 'saml_sessions#create', as: :user_sso_session
get 'saml/sign_out' => 'saml_sessions#destroy', as: :destroy_user_sso_session
get 'saml/metadata' => 'saml_sessions#metadata', as: :metadata_user_sso_session
match 'saml/idp_sign_out' => 'saml_sessions#idp_sign_out', via: [:get, :post]
end
Lastly here is my SamlSessionsController:
require "ruby-saml"
class SamlSessionsController < SessionsController
include DeviseSamlAuthenticatable::SamlConfig
skip_before_filter :verify_authenticity_token, raise: false
before_action :authorize_viewer, except: [:metadata]
protect_from_forgery with: :null_session, except: :create
def new
idp_entity_id = Company.friendly.find(#_request.env['HTTP_HOST'].split('.')[0]).idp_entity_id
request = OneLogin::RubySaml::Authrequest.new
action = request.create(saml_config(idp_entity_id))
redirect_to action
end
def metadata
idp_entity_id = Company.friendly.find(#_request.env['HTTP_HOST'].split('.')[0]).idp_entity_id
meta = OneLogin::RubySaml::Metadata.new
render :xml => meta.generate(saml_config(idp_entity_id)), content_type: 'application/samlmetadata+xml'
end
def create
#idp_entity_id = Company.friendly.find(#_request.env['HTTP_HOST'].split('.')[0]).idp_entity_id
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], settings: saml_config(#idp_entity_id))
if !response.is_valid?
puts "SAML FAILED WITH ERROR: "
puts response.errors
end
super
end
def idp_sign_out
company = Company.friendly.find(request.subdomain.downcase)
idp_entity_id = Company.friendly.find(#_request.env['HTTP_HOST'].split('.')[0]).idp_entity_id
if params[:SAMLRequest] && Devise.saml_session_index_key
saml_config = saml_config(idp_entity_id)
logout_request = OneLogin::RubySaml::SloLogoutrequest.new(params[:SAMLRequest], settings: saml_config(idp_entity_id))
resource_class.reset_session_key_for(logout_request.name_id)
# binding.pry
sign_out current_contact if contact_signed_in?
redirect_to company.after_slo_url.present? ? company.after_slo_url : 'https://' + company.issuer
# redirect_to generate_idp_logout_response(saml_config(idp_entity_id), logout_request.id)
elsif params[:SAMLResponse]
#Currently Devise handles the session invalidation when the request is made.
#To support a true SP initiated logout response, the request ID would have to be tracked and session invalidated
#based on that.
if Devise.saml_sign_out_success_url
redirect_to Devise.saml_sign_out_success_url
else
redirect_to action: :new
end
else
head :invalid_request
end
end
protected
# Override devise to send user to IdP logout for SLO
def after_sign_out_path_for(_)
request = OneLogin::RubySaml::Logoutrequest.new
request.create(saml_config)
end
def generate_idp_logout_response(saml_config, logout_request_id)
OneLogin::RubySaml::SloLogoutresponse.new.create(saml_config, logout_request_id, nil)
end
end
The Problem
When I manually save map the settings from my OneLogin adapter to my Company model (see screenshot), I'm able to authenticate as a user of my app using OneLogin as the identity provider (IdP). However now I need to provide a client with the XML metadata representing the app's setup. When I go to /saml/metadata.xml, I get the following configuration, which according to my client, is incorrect. The client didn't offer any further details about what the problem is. They are using PingFederate, if that matters.
<?xml version='1.0' encoding='UTF-8'?>
<md:EntityDescriptor ID='_a3581975-b73d-4784-a106-bafd61e15f87' xmlns:md='urn:oasis:names:tc:SAML:2.0:metadata'>
<md:SPSSODescriptor AuthnRequestsSigned='false' WantAssertionsSigned='false' protocolSupportEnumeration='urn:oasis:names:tc:SAML:2.0:protocol'>
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</md:NameIDFormat>
<md:AssertionConsumerService Binding='urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' Location='https://mysubdomain.myapp.local:3000/saml/auth' index='0' isDefault='true'/>
</md:SPSSODescriptor>
</md:EntityDescriptor>
My question is, what am I doing wrong here and how can I correct it? As I said, I barely understand how SAML works under the hood.
There is no EntityID defined on that metadata XML.
If you try to verify the XML on a validation tool you will get
Line: 2 | Column: 0 --> Element
'{urn:oasis:names:tc:SAML:2.0:metadata}EntityDescriptor': The
attribute 'entityID' is required but missing.
If you review ruby-saml code, the EntityID is added to the metadata XML if a settings.issuer is defined. Can you verify if that data is provided? Maybe company.issuer that I see at IdPSettingsAdapter class has an empty value.

Savon 2.11.1 set soap header after client is instantiated Rails + SSRS

I'm trying to integrate my rails app with a Sql Server Reports Services (SSRS) using Savon 2.11.1. I'm using the ReportServicesExecution2005 WSDL. The problem I have is that I have to add a session_id to the soap header after the client has been instantiated. This is because the session_id is generated by SSRS after the client makes a call to load_report.
client = Savon.client(wsdl: "https://somewsdl.asmx?wsdl", basic_auth: ["user", "pass"])
this call returns the session_id in the response:
response = client.call(:load_report, message: {report: "path/to/report"} )
the soap header needs to contain the session_id in the client when this call is made:
client.call(:set_execution_parameters, message: { report: "path/to/report", parameters: params } )
Tried this but it didn't work:
client.call(:set_execution_parameters, soap_header: {"session_id" => #session_id}, message: { report: "path/to/report", parameters: params } )
I get the following error:
(soap:Client) The session identifier is missing. A session identifier
is required for this operation. --->
Microsoft.ReportingServices.Diagnostics.Utilities.MissingSessionIdException:
The session identifier is missing. A session identifier is required
for this operation.
Thanks for the help an advance.
You should use the soap_header hash to pass session id like
response = client.call(:set_execution_parameters, :soap_header => { :session_id => sid })
Cheers
The solution I found(while clunky) was to create a new client via Savon and pass the existing client values plus the new execution_id/session_Id into the constructor. This did the trick. Code example below.
Initial Savon Client:
#exeClient = Savon.client(wsdl: "some_wsdl.asmx?wsdl", basic_auth: ["user", "pass"],
convert_request_keys_to: :camelcase, soap_header: {"execution_id" => ""} )
call load_report to get execution_id:
#data = #exeClient.call(:load_report, message: {report: "/path/to/report"} )
Access execution_id:
#report = #data.to_hash
#execution_id = #report[:load_report_response][:execution_info][:execution_id]
Create new client that has access to run the report via the execution_id:
#newClient = Savon.client(wsdl: "/path/to/report/wsdl", basic_auth: ["user", "pass"],
:soap_header => {:"tns:TrustedUserHeader" => {"tns:UserName"=> "" , "tns:UserToken" => ""}, :"tns:ExecutionHeader" => {"tns:ExecutionID" => #execution_id}})
Create Params Hash:
param1 = { :Parameter_value =>{
:"Name" => "nameOfParam",
:"Value" => current_user.company_id
}
}
Call setExecutionParameters:
response = #newClient.call(:set_execution_parameters, message: { "Parameters" => param1} )
Now it works. Note that its important how you specify namespaces with Savon. If you don't do it correctly, the namespace will not be specified on some tags; which will make the soap call fail.

Perform authentication to Polarion webservice with Savon

I am attempting to follow the discussion here using Ruby and Savon. I am able to retrieve a session ID, but whenever I perform a request from the clients that require authentication (tracker), I receive an Authorization Failed error.
require 'Savon'
tracker_url = 'http://myserver/polarion/ws/services/TrackerWebService?wsdl'
session_url = 'http://myserver/polarion/ws/services/SessionWebService?wsdl'
# todo handle bad login credentials gracefully
session_client = Savon.client(wsdl: session_url)
response = session_client.call(:log_in, message: {user_name: 'lsimons', password: 'mypassword'})
session_id = response.header[:session_id]
puts "Session ID: #{session_id}"
tracker_client = Savon.client(wsdl: tracker_url, soap_header: {"session" => session_id}, headers: {"sessionID" => session_id})
puts "Requesting Workitem"
begin
tracker_client.call(:get_work_item_by_id, message: {project_id: 'myProject', workitem_id: 'myWorkitem'})
rescue
puts "Client call failed"
end
This code creates the following SOAP request for the tracker_client:
<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ins0="http://ws.polarion.com/TrackerWebService-impl" xmlns:ins1="http://ws.polarion.com/types" xmlns:ins2="http://ws.polarion.com/TrackerWebService-types" xmlns:ins3="http://ws.polarion.com/ProjectWebService-types" xmlns:tns1="http://ws.polarion.com/TrackerWebService" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<env:Header>
<session>2164640482421325916</session>
</env:Header>
<env:Body>
<tns1:getWorkItemById>
<ins0:projectId>myProject</ins0:projectId>
<ins0:workitemId>myWorkitem</ins0:workitemId>
</tns1:getWorkItemById>
</env:Body>
</env:Envelope>
However, in the forum discussion, the sessionID element occurs before the header. I didn't think this was possible with standard SOAP? Is there a way to achieve this with Savon or am I misinterpreting the forum discussion?
I faced the same problem following the same thread. This is how I made it work (by replicating the response headers of the log_in request):
tracker_client = Savon.client(
wsdl: tracker_url,
soap_header: {
"ns1:sessionID" => session_id,
:attributes! => {
"ns1:sessionID" => {
"env:actor" => "http://schemas.xmlsoap.org/soap/actor/next",
"env:mustUnderstand" => "0",
"xmlns:ns1" => "http://ws.polarion.com/session"
}
}
}
)
Old question but thought I can add some info to hopefully help somebody.
I am using lolsoap to talk to polarion. In the above resulting document, the sessionID element is wiped off any namespaces and attributes. Also the assessment is right that actor and mustUnderstand attributes seem irrelevant.
To add header properly though with all fluff, one needs to get the Nokogiri::XML::Node and dup it, then add it to the header of the doc. This is a bug in nokogiri/libxml2 that adding child elements can often break namespaces unless Node is cloned before adding [1].
In lolsoap it is done something like:
auth_header = login_response.nokogiri_doc.xpath("//*[local-name()='sessionID']")[0].dup
other_request.header.__node__ << auth_header
Please note the dup operation. header.__node__ is just the header Nokogiri::XML::Element of a random SOAP request.
The dup operation makes adding desired element into another one with all necessary namespaces and attributes properly defined.
I don't know if savon allows one to directly touch request XML but I guess it does. Thus HTH
[1] https://github.com/sparklemotion/nokogiri/issues/1200