Rails request.create in rsa-sha256? - ruby-on-rails

I am working on SAML authentication
I have settings with digest and signature method in rsa-sha256, but when I create the request to redirect a authenticating user the request is in rsa-sha1 ...
In the url, there is SigAlg=http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1
But I want it to be rsa-sha256
Settings :
def saml_settings
settings = OneLogin::RubySaml::Settings.new({:idp_cert_fingerprint_algorithm => XMLSecurity::Document::SHA256})
settings.assertion_consumer_service_url = "..."
settings.issuer = "..."
settings.idp_sso_target_url = "..."
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
#settings.authn_context = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
settings.certificate = CONFIG_CERTIFICATE
settings.private_key = CONFIG_PRIVATE_KEY
settings.security[:authn_requests_signed] = true # Enable or not signature on AuthNRequest
settings.security[:logout_requests_signed] = true # Enable or not signature on Logout Request
settings.security[:logout_responses_signed] = true # Enable or not signature on Logout Response
settings.security[:digest_method] = XMLSecurity::Document::SHA256
settings.security[:signature_method] = XMLSecurity::Document::SHA256
settings.security[:embed_sign] = false
settings
end
and when I create the request :
request = OneLogin::RubySaml::Authrequest.new
redirect_to(request.create(saml_settings))
Here, request.create(saml_settings) is returning the In the url, there is SigAlg=http%3A%2F%2Fwww.w3.org%2F2000%2F09%2Fxmldsig%23rsa-sha1
How can I change this to be in rsa-sha256?

Actually, I found the anwser :
In the lib, the sha1 ou sha256 ecryption will be defined by the settings,
in the method create_params of OneLogin::RubySaml::Authrequest we have
if settings.security[:authn_requests_signed] && !settings.security[:embed_sign] && settings.private_key
params['SigAlg'] = XMLSecurity::Document::SHA1
...
end
so, I had
settings.security[:embed_sign] = false
And so the condition was TRUE. But it has to be FALSE
so I've put
settings.security[:embed_sign] = true
(actually, it has to be at true)
and
request.create(saml_settings, {:SigAlg => XMLSecurity::Document::SHA256}))
And it's done !
( cf http://www.rubydoc.info/github/onelogin/ruby-saml/OneLogin/RubySaml/Authrequest#create-instance_method )

Related

Why does HERE oauth2 token request api return 401300 with Rails but works fine with Postman?

When calling the HERE authentication service (https://account.api.here.com/oauth2/token) from one of the controllers of the RoR APP (Rails 5.0.6/ruby 2.6.1) I get a 401: "401300 Signature mismatch. Authorization signature or client credential is wrong"
The Key, secret, Authorization header, content type, request body etc ... are the same as the ones used by Postman.
Postman always returns a 200 OK but the rails app systematically returns "401"
Any suggestions on what the problem is?
def fetch_new_token
# URL
api_url = 'https://account.api.here.com/oauth2/token'
# VERSION
api_version='1.0'
# GRANT TYPE
api_grant_type_for_req_body='grant_type=client_credentials'
#KEY
api_access_key_id = CGI.escape(ENV['my_access_key_id'])
#SECRET
api_access_key_secret = CGI.escape(ENV['my_access_key_secret'])
#NONCE
draft_api_nonce= [('a'..'z'), ('A'..'Z')].map(&:to_a).flatten
api_nonce=(0...20).map { draft_api_nonce[rand(draft_api_nonce.length)] }.join
#TMESTAMP
api_timestamp = (Time.now).strftime('%s')
#NORMALIZED URL
api_url_normalized = CGI.escape(api_url)
#SIGNING METHOD
api_signature_method= CGI.escape('HMAC-SHA256')
#OAUTH PARAMETERS BASE STRING
api_parameters_string=('consumer_key='+api_access_key_id+'&nonce='+api_nonce+'&signature_method='+api_signature_method+'&timestamp='+api_timestamp+'&'+'version=1.0')
#ENCODED BASE STRING
api_normalized_string = 'POST&'+api_url_normalized+'&'+api_grant_type_for_req_body+CGI.escape('&'+api_parameters_string)
#SIGNNG KEY
api_signing_key = api_access_key_secret+'&'
#SIGNATURE
digest = OpenSSL::Digest.new('sha256')
api_signature = OpenSSL::HMAC.hexdigest(digest, api_normalized_string, api_signing_key)
# convert the HASHING result to a URL ENCODED base64 string.
api_signature_encoded = (Base64.strict_encode64(api_signature))
# AUTHORIZATION STRING - ESCAPED
api_authorization_string = ('OAuth consumer_key="'+api_access_key_id+'",signature_method="'+api_signature_method+'",timestamp="'+CGI.escape(api_timestamp)+'",nonce="'+CGI.escape(api_nonce)+'",version="'+CGI.escape(api_version)+'",signature="'+CGI.escape(api_signature_encoded)+'"')
# FARADAY OBJECT
connect_token_request = Faraday.new(url: 'https://account.api.here.com') do |faraday|
faraday.response :logger, nil, bodies: true
faraday.request :json
faraday.headers['Accept'] = 'application/json'
faraday.headers['Content-Type'] = 'application/x-www-form-urlencoded'
faraday.headers['Authorization'] = api_authorization_string
faraday.adapter Faraday.default_adapter
end
# FARADAY POST
response_token_request= connect_token_request.post('/oauth2/token', 'grant_type=client_credentials' )
# CHECK THE RESULT
puts response_token_request.body
#json = JSON.parse(response_token_request.body)
req_status = #json['httpStatus']
puts "The status returned in the body is:::: #{req_status}"
puts "===== ///// ======"
puts "===== ///// ======"
req_error_code = #json['errorCode']
puts "The ERROR CODE returned in the body is:::: #{req_error_code}"
end
I don't know RoR but I had the same problem in Javascript and this script solved my problem:
const axios = require('axios')
const cryptoJS = require('crypto-js');
const btoa = require('btoa');
exports.getToken = (app_key, app_secret) => {
let url = "https://account.api.here.com/oauth2/token";
let key = encodeURI(app_key);
let secret = encodeURI(app_secret);
let nonce = btoa(Math.random().toString(36)).substring(2, 13);
let timestamp = Math.floor(Date.now()/1000);
let normalizedUrl = encodeURIComponent(url);
let signing_method = encodeURI("HMAC-SHA256");
let sig_string = "oauth_consumer_key="
.concat(key)
.concat("&oauth_nonce=")
.concat(nonce)
.concat("&oauth_signature_method=")
.concat(signing_method)
.concat("&oauth_timestamp=")
.concat(timestamp)
.concat("&").concat("oauth_version=1.0");
let normalised_string = "POST&".concat(normalizedUrl).concat("&").concat(encodeURIComponent(sig_string));
let signingKey = secret.concat("&");
let digest = cryptoJS.HmacSHA256(normalised_string, signingKey);
let signature = cryptoJS.enc.Base64.stringify(digest);
let auth = 'OAuth oauth_consumer_key="'
.concat(key)
.concat('",oauth_signature_method="')
.concat(signing_method)
.concat('",oauth_signature="')
.concat(encodeURIComponent(signature))
.concat('",oauth_timestamp="')
.concat(timestamp)
.concat('",oauth_nonce="')
.concat(nonce)
.concat('",oauth_version="1.0"')
return axios({
method: 'post',
url: url,
data: JSON.stringify({grantType: "client_credentials"}),
headers: {
'Content-Type': "application/json",
'Authorization': auth
}
});
}

Ruby Rails Devise and SAML with Office 365

I have setup a new Rails application using devise and devise_saml_authenticatable to authenticate against Office 365.
The login unfortunately shows following error message:
Sign in
Sorry, but we’re having trouble signing you in.
AADSTS7500522: XML element 'AuthnContextClassRef' in XML namespace 'urn:oasis:names:tc:SAML:2.0:assertion' in the SAML message must be a URI.
My config/decise.rb file looks as follows:
config.saml_create_user = true
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 = nil
config.saml_configure do |settings|
settings.assertion_consumer_service_url = "https://localhost:3000/users/saml/auth"
settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
settings.name_identifier_format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
settings.issuer = "https://localhost:3000/saml/metadata"
settings.authn_context = ""
settings.idp_slo_target_url = "https://login.microsoftonline.com/common/wsfederation?wa=wsignout1.0"
settings.idp_sso_target_url = "https://login.microsoftonline.com/xxx/saml2"
settings.idp_cert_fingerprint = "E4:....."
settings.idp_cert_fingerprint_algorithm = "http://www.w3.org/2000/09/xmldsig#sha1"
end
and the "Reply URL (Assertion Consumer Service URL)" in the Azure configuration is set to
https://localhost:3000/users/saml/auth
Any ideas how to fix this?
Finally figured it out: All the Devise examples have
settings.authn_context = ""
set. If I set it to
settings.authn_context = "urn:oasis:names:tc:SAML:2.0:ac:classes:Password"
then the error disappears.

Trouble with low level caching in rails

I have some issues with caching in rails. I don't find how should i setting it.
Here's the code :
submit_key = nil
pairs_email = Hash.new
pairs_type = Rails.cache.fetch("cache_typeform", :expires_in => 1.day) do
(0..9).each do
if submit_key.present?
url = "https://api.typeform.com/forms/#{typeform_id}/responses?page_size=1000&until=#{submit_key}"
response = RestClient.get url, {:Authorization => 'Bearer XXXXXXXXXXX'}
parsed = JSON.parse(response.body)
else
response = RestClient.get "https://api.typeform.com/forms/#{typeform_id}/responses?page_size=1000", {:Authorization => 'Bearer XXXXXXXXXXXXXXX}
parsed = JSON.parse(response.body)
end
parsed['items'].each do |item|
pairs_email[item['hidden']['email']] = item['token'] if item['hidden']['email'].present?
end
submit_key = parsed['items'][-1]['submitted_at'].chop
end
end
Then it should return a pairs containing an email and an ID and this pairs is used after to get more informations. However, nothing is returning.
Does someone can tell me what I've done wrong in my code? Am I missing something somewhere?
UPDATE
I want to use my cache for getting informations from the typeform API :
results = Hash.new
if pairs_email[email].present?
url = "https://api.typeform.com/v1/form/#{typeform_id}?key=#{ENV['TYPEFORM_API_KEY']}&token=#{pairs_email[email]}"
response = RestClient.get(url)
parsed = JSON.parse(response.body)
results["email"] = parsed["responses"][0]["hidden"]["email"] # Email
results["first_name"] = parsed["responses"][0]["answers"]["textfield_25078009"] # prénom
results["last_name"] = parsed["responses"][0]["answers"]["textfield_25078014"] # nom
results["phone_number"] = parsed["responses"][0]["answers"]["textfield_25444504"] #N°
results["job"] = parsed["responses"][0]["answers"]["textfield_24904749"] # métier
results["status_legal"] = parsed["responses"][0]["answers"]["list_24904751_choice"] # statut légal ?
results["birthdate"] = parsed["responses"][0]["answers"]["date_24904754"] # Date de naissance
results["zipcode"] = parsed["responses"][0]["answers"]["number_24904755"] # Code postal
results["has_partner"] = parsed["responses"][0]["answers"]["yesno_53894471"] # has_partner
results["children"] = parsed["responses"][0]["answers"]["list_53894494_choice"] # Nombre d'enfants
results["optical_option"] = parsed["responses"][0]["answers"]["list_24904752_choice_32209601"] # optical_option
results["dental_option"] = parsed["responses"][0]["answers"]["list_24904752_choice_32209602"] # dental_option
results["sick_15d"] = parsed["responses"][0]["answers"]["list_24904752_choice_32209603"] # Sick_15d
results["target_year"] = parsed["responses"][0]["answers"]["list_24905736_choice"] # target_year
results["monthly_income"] = parsed["responses"][0]["answers"]["number_24904756"] # monthly_income
results["independent"] = parsed["responses"][0]["answers"]["yesno_53895024"] # independent_1_year
#results["subject_to_discuss"] = parsed["responses"][0]["answers"]["textarea_24904759"] # Avez-vous des sujets dont vous voulez discuter
end
Here's something you must try before getting caching right. Attaching a screenshot from my machine.
Also if you are in development environment you would need to enable caching to see the effect. You could add config.action_controller.perform_caching = true and config.cache_store = :memory_store, { size: 64.megabytes } to your development.rb config file to enable caching.
This is just an idea of how caching happens and check if it really works, this should help you get going with your task.
Rails.cache.fetch stores the value evaluated from the block passed into this method (if there is one, of course). In your example, you're returning (0..9) range from the block, instead of actually evaluated [email id] pairs.

Facebook Auth log out with Rails and Parse

I am having trouble finding a way to log out from facebook. We are logging in using window.location = '/auth/facebook' which redirects the user to a facebook login page, and calls a callback route /auth/facebook/callback(declared in developers.facebook.com) to a method in my session_controller/create.
The session_controller/create method looks like this
def create
#get auth hash from omniauth
auth = auth_hash
#data = {}
#session_data = {}
#data['id'] = auth['uid']
#data['access_token'] = auth['credentials']['token']
#time must be in iso format, see parse rest api for details under linking
#expiry = Time.at(auth['credentials']['expires_at']).iso8601
#data['expiration_date'] = #expiry
#create new linking user object
user = Parse::User::Facebook.new(#data)
# add other user information to body of PARSE::USER::FACEBOOK instance
user.body['email'] = auth['info']['email']
user.body['auth'] = true
user.body['fullname'] = auth['info']['name']
user.body['ip'] = request.remote_ip
user.body['image'] = auth['info']['image'] if auth['info']['image']
user.body['admin'] = false
user.body['facebookId'] = auth['uid']
user.body['facebookToken'] = auth['credentials']['token']
user.body['has_seen_web_tutorial'] = false
#current_user = user.save
#save necessary session information
session['name'] = #current_user['fullname']
session['points'] = #current_user['points']
session['objectId'] = #current_user['objectId']
session['sessionToken'] = #current_user['sessionToken']
session['image'] = #current_user['image']
session['location'] = #current_user['location']
#session_data['name'] = session['name']
#session_data['fullname'] = #current_user['fullname']
#session_data['points'] = session['points']
#session_data['objectId'] = session['objectId']
#session_data['image'] = session['image']
if auth['info']['image']
File.open('temp_face.png', 'wb') do |file|
file << open(auth['info']['image'], :allow_redirections => :safe).read
end
photo = Parse::File.new({
:body => IO.read("temp_face.png"),
:local_filename => "temp_face.png",
:content_type => "image/png"
})
photo.save
#user.body['image'] = photo
end
image = Parse::Object.new("ImageUploads")
image['fileKey'] = photo
image['owner'] = Parse::Pointer.new({"className" => "_User", "objectId" => session['objectId']})
image['type'] = 'profile'
#image['location']
saved_image = image.save
redirect_to listing_index_path
end
I am having trouble trying to log out. Very new to rails. Any help please?
Thank you

Can't add product to Shopify via Ruby code but can through rails console

I have the following code
shopify_store_name = ShopifyStore.where(:store_id => 98).pluck('shop').first
shopify_access_token = ShopifyStore.where(:store_id => 98).pluck('access_token').first
session = ShopifyAPI::Session.new("#{shopify_store_name}.myshopify.com", shopify_access_token)
ShopifyAPI::Base.activate_session(session)
shop = ShopifyAPI::Shop.current
# Create a new product
new_product = ShopifyAPI::Product.new
new_product.title = 'This is a new Product'
new_product.product_type = 'Type'
new_product.vendor = 'Vendor'
new_product.save
Which when run causes : Failed. Response code = 403. Response message = Forbidden.
However if I copy and paste the code in to my rails console window it works perfectly? Any one got any idea what that would be?
Thanks in advance!
403 will usually happen for permission issues.
You probably don't have the permission write_products for that token.
For those getting this error, you need to set up the permission in your shopify_app.rb initializer (in the initializers folder under config) under the scope field:
ShopifyApp.configure do |config|
config.application_name = "My Shopify App"
config.api_key = ENV['SHOPIFY_API_KEY']
config.secret = ENV['SHOPIFY_API_SECRET']
config.old_secret = ""
config.scope = "read_products,read_customers" # Consult this page for more scope options:
# https://help.shopify.com/en/api/getting-started/authentication/oauth/scopes
config.reauth_on_access_scope_changes = true
config.embedded_app = true
config.after_authenticate_job = false
config.api_version = "2020-04"
config.shop_session_repository = 'Shop'
config.allow_jwt_authentication = true
config.webhooks = [
{topic: 'app/uninstalled', address: "#{ENV['APP_URL']}/webhooks/app_uninstalled", format: 'json'},
]
end

Resources