How to create a JWT for use with Apple Music - ruby

Im trying to create a developer token that is a ES256 JWT to use for Apple Music authentication. (Here)
Im using ruby and the JWT gem but after creating the token I get a 401 error when authenticating with Apple Music
require 'jwt'
payload = {:iss => 'CapExdTeam', :iat => '1497335982', :exp => '1513112982'}
priv = "-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgU208KCg/doqiSzsVF5sknVtYSgt8/3oiYGbvryIRrzSgCgYIKoZIzj0DAQehRANCAAQfrvDWizEnWAzB2Hx2r/NyvIBO6KGBDL7wkZoKnz4Sm4+1P1dhD9fVEhbsdoq9RKEf8dvzTOZMaC/iLqZFKSN6
-----END PRIVATE KEY-----"
ecdsa_key = OpenSSL::PKey::EC.new(priv)
token = JWT.encode payload, ecdsa_key, 'ES256', { :kid => "CapExedKid", :alg => "ES256" }
puts token
`curl -v -H 'Authorization: Bearer #{token}' "https://api.music.apple.com/v1/catalog/us/songs/203709340"
Im using the sample private key to simulate 429 error just for illustration purposes

I used this script and it works perfectly
https://github.com/pelauimagineering/apple-music-token-generator

Based on #DanDevine's answer, here's a more Ruby/OO approach:
require "openssl"
# Example:
#
# token = AppleMusic::Token.new(key_id: "...", team_id: "...", keyfile: File.new("lib/assets/AuthKey_xxxxxxx.p8"))
# token.auth_token
# token.auth_header
#
module AppleMusic
class Token
attr_reader :key_id, :team_id, :keyfile
# Keyfile should be an IO type that responds to `read`
def initialize(key_id:, team_id:, keyfile:)
#key_id = key_id
#team_id = team_id
#keyfile = keyfile
end
def auth_token
#auth_token ||= fetch_auth_token
end
def auth_header
"Bearer #{auth_token}"
end
protected
def fetch_auth_token
header = {
typ: "JWT", # Must be specified; not in documentation
alg: "ES256",
kid: key_id
}
body = {
iss: team_id,
iat: Time.now.to_i,
exp: Time.now.to_i + 43_200 # 12hrs
}
JWT.encode(body, auth_key, 'ES256', header)
end
def auth_key
key = OpenSSL::PKey::EC.new(keyfile.read)
key.check_key
key
end
end
end

It's now also possible in pure Swift!
You first have to create a MusicKit identifier and a private key using this guide from Apple. Then a token can be easily created using Swift-JWT from IBM in pure Swift.
It's more or less just an invocation of the SwiftJWT API:
let teamId = "yourTeamID"
let keyId = "yourKeyID"
let keyFileUrl = URL(fileURLWithPath:"/pathToYour/key.p8")
struct MyClaims: Claims {
let iss: String
let iat: Date?
let exp: Date?
}
let myHeader = Header(kid: keyId)
let myClaims = MyClaims(iss: teamId, iat: Date(), exp: Date() + 24 * 60 * 60)
var myJWT = SwiftJWT.JWT(header: myHeader, claims: myClaims)
let token = try! myJWT.sign(using: .es256(privateKey: try! String(contentsOf: keyFileUrl).data(using: .utf8)!))
I created a simple example and a command line tool using the Swift Package Manager: SwiftJWTSample

Here's a working Ruby implementation. Call with your keyId and teamId, provide access to your private key file and go.
class AppleMusic
#auth_token
#validity_start
#validity_end
def initialize(keyId, teamId, options ={})
appleKeyId = keyId
appleTeamId = teamId
#validity_start = Time.now.to_i
#validity_end = Time.now.to_i + 43200 # 12 hours in seconds...
# Build up the headers
header = {
'typ' => 'JWT', # MUST BE SPECIFIED... Apple doesn't tell you this!
'alg' => 'ES256',
'kid' => appleKeyId
}
# Build up the payload
body = {
'iss' => appleTeamId,
'iat' => #validity_start,
'exp' => #validity_end
}
# This should be installed manually on the server somewhere
# TODO: Add some protection around the file's existance, set the name & location
# as some type of configuration key.
file = File.read('lib/assets/AuthKey_xxxxxxx.p8')
key = OpenSSL::PKey::EC.new(file)
key.check_key
#auth_token = JWT.encode(body, key, 'ES256', header)
#auth_token
end
def auth_token
#auth_token
end
def auth_header
"Bearer #{#auth_token}"
end
def validity_start
#validity_start
end
def validity_end
#validity_end
end
end

Related

How to get image classification prediction from GCP AIPlatform in ruby?

I'm new with ruby and I want to use GCP AIPlatform but I'm struggeling with the payload.
So far, I have :
client = ::Google::Cloud::AIPlatform::V1::PredictionService::Client.new do |config|
config.endpoint = "#{location}-aiplatform.googleapis.com"
end
img = File.open(imgPath, 'rb') do |img|
'data:image/png;base64,' + Base64.strict_encode64(img.read)
end
instance = Instance.new(:content => img)
request = Google::Cloud::AIPlatform::V1::PredictRequest.new(
endpoint: "projects/#{project}/locations/#{location}/endpoints/#{endpoint}",
instances: [instance]
)
result = client.predict request
p result
Here is my proto
message Instance {
required bytes content = 1;
};
But I have the following error : Invalid type Instance to assign to submessage field 'instances'
I read the documentation but for ruby SDK it's a bit light.
The parameters are OK, the JS example here : https://github.com/googleapis/nodejs-ai-platform/blob/main/samples/predict-image-object-detection.js is working with those parameters
What am I doing wrong ?
I managed it
client = Google::Cloud::AIPlatform::V1::PredictionService::Client.new do |config|
config.endpoint = "#{location}-aiplatform.googleapis.com"
end
img = File.open(imgPath, 'rb') do |img|
Base64.strict_encode64(img.read)
end
instance = Google::Protobuf::Value.new(:struct_value => {:fields => {
:content => {:string_value => img}
}})
endpoint = "projects/#{project}/locations/#{location}/endpoints/#{endpoint}"
request = Google::Cloud::AIPlatform::V1::PredictRequest.new(
endpoint: endpoint,
instances: [instance]
)
result = client.predict request
p result
The use of the Google::Protobuf::Value looks ugly to me but it works

Xeroizer partner application: renew access token not working Rails 4.2.0 Ruby 2.2.9

I am using a ruby gem Xeroizer and partner application https://github.com/waynerobinson/xeroizer and generated pem file as instructed in this link. when I renew access token to obtain long term connection then upon token renewal everything sets up "#expires_at" also shows extended expiry on xero side but session got disconnected after 30 minutes.New returned token and secret are not same and I am saving these new ones in the database also. Here is my code below.
//////// connecting
def connect_xero_organisation
#xeroizer = Xeroizer::PartnerApplication.new(ENV["XERO_GATEWAY_CONSUMER_KEY"], ENV["XERO_GATEWAY_CONSUMER_SECRET"], "#{Rails.root}/config/certs/privatekey.pem")
request_token = #xeroizer.request_token(oauth_callback: "https://{ENV['HOST_NAME']}/companies/#{#company.slug}/xero")
session[:request_token] = request_token.token
session[:request_secret] = request_token.secret
redirect_to request_token.authorize_url
end
///////// callback method
def xero_organisation_detail
if params[:oauth_verifier].present?
#xeroizer = Xeroizer::PartnerApplication.new(ENV["XERO_GATEWAY_CONSUMER_KEY"], ENV["XERO_GATEWAY_CONSUMER_SECRET"], "#{Rails.root}/config/certs/privatekey.pem")
begin
#xeroizer.authorize_from_request(session[:request_token], session[:request_secret], oauth_verifier: params[:oauth_verifier])
session[:xero_auth] = { access_token: #xeroizer.access_token.token,access_secret: #xeroizer.access_token.secret }
session.delete(:request_token)
session.delete(:request_secret)
xero_organisation_create_update(#xeroizer)
redirect_to edit_company_path(#company, xero: 'xero'), notice: "Settings have been saved"
rescue Exception => e
redirect_to edit_company_path(#company)
end
else
#organisation_detail = #company.xero_organisation
#xero_organisation_accounts_details = #company.xero_organisation_accounts
end
end
///////// #company is simply setting in before_filter :set_company
def xero_organisation_create_update(xeroizer)
organisation = xeroizer.Organisation.all(:find => {organisation_id: params[:org]})
connection_expires_at = xeroizer.client.expires_at
token = xeroizer.access_token.token
secret = xeroizer.access_token.secret
session_handle = xeroizer.session_handle
organisation_name = organisation.last.name
code = organisation.last.short_code
if #company.xero_organisation.present?
#company.xero_organisation.update_attributes(name: organisation_name, connection_expires_at: connection_expires_at, short_code: code, xero_token: token, xero_secret: secret, conn_handle: session_handle)
#organisation_detail = #company.xero_organisation
else
#organisation_detail = XeroOrganisation.create(name: organisation_name,connection_expires_at: connection_expires_at,company_id: #company.id,short_code: code, xero_token: token, xero_secret: secret, conn_handle: session_handle)
end
end
////// renewal
cronjob method for renewal
def self.conn_renewel
XeroOrganisation.where("connection_expires_at is not NULL").each do |xo|
client = Xeroizer::PartnerApplication.new(ENV["XERO_GATEWAY_CONSUMER_KEY"], ENV["XERO_GATEWAY_CONSUMER_SECRET"], "#{Rails.root}/config/certs/privatekey.pem")
conn = client.renew_access_token(xo.access_token,xo.access_secret,xo.session_handle)
xo.update_attributes(:xero_token=>conn.first, :xero_secret=>conn.last)
end
end
Special thanks! in advance.

PayPal express ActiveMerchant gateway not working

According to this, the ActiveMerchant PayPal Express Gateway is initialized like this:
paypal_options = {
login: "API_USERNAME_HERE",
password: "API_PASSWORD_HERE",
signature: "API_SIGNATURE_HERE"
}
::EXPRESS_GATEWAY = ActiveMerchant::Billing::PaypalExpressGateway.new(paypal_options)
I'm definitely supplying a signature, yet I'm getting this error:
An API Certificate or API Signature is required to make requests to PayPal
The PayPal initializer looks like this (found here):
def initialize(options = {})
requires!(options, :login, :password)
headers = {'X-PP-AUTHORIZATION' => options.delete(:auth_signature), 'X-PAYPAL-MESSAGE-PROTOCOL' => 'SOAP11'} if options[:auth_signature]
options = {
:pem => pem_file,
:signature => signature,
:headers => headers || {}
}.update(options)
if options[:pem].blank? && options[:signature].blank?
raise ArgumentError, "An API Certificate or API Signature is required to make requests to PayPal"
end
super(options)
end
I don't understand what this initializer is doing with the signature and why it's not accepting it as per the example.
Here are the options I'm passing, which I've put to STDOUT:
{
"password" =>"***************",
"signature" =>"AVtrAKGQXoUNJFduUU0pn1dewq80AK9KYWenyFwYcduz8elS85B8T0Wc",
"allow_guest_checkout" =>true,
"login" =>"********************",
"test" =>true
}
Can someone help me with this please?
Note that I'm using this in JRuby, but I don't think that makes any difference in this case.
EDIT after #PiersC's comments:
I hardcoded this instead of taking them as params from Java and it worked:
options = {
login: "*************",
password: "*****************",
signature: "AVtrAKGQXoUNJFduUU0pn1dewq80AK9KYWenyFwYcduz8elS85B8T0Wc"
}
However this has led to another question. I've been converting the Java maps to Ruby hashes like this:
def self.convert_hash(map)
hsh = {}
map.each {|key, value| hsh[key] = value}
hsh.with_indifferent_access
end
And this has worked on all other gateways. How do I convert the Java map correctly to the options hash in Ruby?
Your option keys are strings but should be symbols, eg. { password: '***', ... } ActiveSupport::HashWithInvalidAccess hides (obscures?) the difference between symbol keys and string keys, but if you are using a regular Hash then { 'signature' => signature } is not the same as { signature: signature }.

Accessing Ivona Speech Cloud with Ruby

I am trying to access Ivona Speech Cloud using Ruby.
I have ported one of the code examples I found to Ruby, but I probably did something wrong at authenticating the request, since I am getting an error.
This is my implementation:
require 'http' # the gem
require 'openssl'
require 'pp'
def sign key, date, region, service, text
k_date = OpenSSL::HMAC.digest('sha256', "AWS4" + key, date)
k_region = OpenSSL::HMAC.digest('sha256', k_date, region)
k_service = OpenSSL::HMAC.digest('sha256', k_region, service)
k_signing = OpenSSL::HMAC.digest('sha256', k_service, "aws4_request")
signature = OpenSSL::HMAC.digest('sha256', k_signing, text)
signature
end
def run
access_key = "GDxxxxxxxxxxxxxxxxRA"
secret_key = "QtxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxlE"
region = 'eu-west-1'
date = '20160808'
service = 'tts'
body = {"Input" => {"Data" => "Hello world"}}
endpoint = "https://#{service}.#{region}.ivonacloud.com/CreateSpeech"
signature = sign secret_key, date, region, service, 'Hello World'
headers = {
"Content-Type" =>"application/json",
"Authorization" => "AWS4-HMAC-SHA256",
"Credential" => "#{access_key}/#{date}/#{region}/#{service}/aws4_request",
"SignedHeaders" => "content-type;host;x-amz-content-sha256;x-amz-date",
"Signature" => "#{signature}",
}
res = HTTP.headers(headers).post(endpoint, json: body)
p res
end
run
This is the error I am getting (line broken for legibility):
#<HTTP::Response/1.1 403 Forbidden
{"X-Amzn-Requestid"=>"18a44dd8-6dc3-11e6-808f-975692d1ee55",
"X-Amzn-Errortype"=>"IncompleteSignatureException:http://internal.amazon.com/coral/com.amazon.coral.service/",
"Content-Type"=>"application/json",
"Content-Length"=>"293",
"Date"=>"Mon, 29 Aug 2016 08:32:18 GMT"}>
Any assistance is appreciated
I would suggest using the AWS4 gem to help with this. I've made similar calls using the following format:
signer = ::AWS4::Signer.new(
access_key: "YOUR_ACCESS_KEY",
secret_key: "YOUR_SECRET_KEY",
region: "us-east-1"
)
aws_headers = {
"Content-Type" => "application/json; charset=utf8",
"Date" => Time.now.iso8601.to_s,
"Host" => "tts.us-east-1.ivonacloud.com"
}
uri = URI(endpoint)
body_params = {"Input":{"Data":"Hello world"}}.to_json
headers = signer.sign("POST", uri, aws_headers, body_params)
res = HTTP.headers(headers).post(endpoint, body: body_params)

How do I access the Magento REST API with Ruby?

I want to start working with Magento's REST API, but I can't seem to get it working.
To start with I need to get access tokens, here's what I'm trying:
require 'oauth'
#consumer = OAuth::Consumer.new("4200100753b2c8b03bde1f5b062c5a80", "c06abdcb734c85dfd7bb115c6a67ae4d", {:site=>"http://178.62.173.99/"})
#request_token = #consumer.get_request_token
# => oauth-0.4.7/lib/oauth/consumer.rb:216:in `token_request': 404 Not Found (OAuth::Unauthorized)
But I keep getting a 404 error.
What should I try next?
Here's a Ruby module I've written to create an access token for the Magento REST API:
module Token
def create_consumer
OAuth::Consumer.new(
CONSUMER_KEY,
CONSUMER_SECRET,
:request_token_path => '/oauth/initiate',
:authorize_path=>'/admin/oauth_authorize',
:access_token_path=>'/oauth/token',
:site => URL
)
end
def request_token(args = {})
args[:consumer].get_request_token(:oauth_callback => URL)
end
def get_authorize_url(args = {})
args[:request_token].authorize_url(:oauth_callback => URL)
end
def authorize_application(args = {})
m = Mechanize.new
m.get(args[:authorize_url]) do |login_page|
auth_page = login_page.form_with(:action => "#{URL}/index.php/admin/oauth_authorize/index/") do |form|
form.elements[1].value = ADMIN_USERNAME
form.elements[2].value = ADMIN_PASSWORD
end.submit
authorize_form = auth_page.forms[0]
#callback_page = authorize_form.submit
end
#callback_page.uri.to_s
end
def extract_oauth_verifier(args = {})
callback_page = "#{args[:callback_page]}".gsub!("#{URL}/?", '')
callback_page_query_string = CGI::parse(callback_page)
callback_page_query_string['oauth_verifier'][0]
end
def get_access_token(args = {})
args[:request_token].get_access_token(:oauth_verifier => args[:oauth_verifier])
end
def save_tokens_to_json(args = {})
auth = {}
auth[:time] = Time.now
auth[:token] = args[:access_token].token
auth[:secret] = args[:access_token].secret
File.open("#{args[:path]}#{args[:filename]}.json", 'w') {|f| f.write(auth.to_json)}
auth
end
def get_new_access_tokens
new_consumer = self.create_consumer
new_request_token = self.request_token(consumer: new_consumer)
new_authorize_url = self.get_authorize_url(request_token: new_request_token)
authorize_new_application = self.authorize_application(authorize_url: new_authorize_url)
extract_new_oauth_verifier = self.extract_oauth_verifier(callback_page: authorize_new_application)
new_access_token = self.get_access_token(request_token: new_request_token, oauth_verifier: extract_new_oauth_verifier)
save_tokens_to_json(filename: 'magento_oauth_access_tokens', path: '/', access_token: new_access_token)
return 'Successfully obtained new access tokens.'
end
end
Run #get_new_access_tokens to get an access token.
Don't forget to define the following variable:
CONSUMER_KEY
CONSUMER_SECRET
URL
ADMIN_USERNAME
ADMIN_PASSWORD
Check out mage on rails. It should work right out of the box. Check out this page for annotated ruby code showcasing the oauth flow

Resources