Sinatra doesn't know this ditty? - ios

I am trying to setup the Spotify IOS API but everytime I run this Ruby file and go to http://localhost:1234/swap I get "Sinatra doesn't know this ditty".
Here is my code:
require 'sinatra'
require 'net/http'
require 'net/https'
require 'base64'
require 'json'
require 'encrypted_strings'
# This is an example token swap service written
# as a Ruby/Sinatra service. This is required by
# the iOS SDK to authenticate a user.
#
# The service requires the Sinatra and
# encrypted_strings gems be installed:
#
# $ gem install sinatra encrypted_strings
#
# To run the service, enter your client ID, client
# secret and client callback URL below and run the
# project.
#
# $ ruby spotify_token_swap.rb
#
# IMPORTANT: The example credentials will work for the
# example apps, you should use your own in your real
# environment. as these might change at any time.
#
# Once the service is running, pass the public URI to
# it (such as http://localhost:1234/swap if you run it
# with default settings on your local machine) to the
# token swap method in the iOS SDK:
#
# NSURL *swapServiceURL = [NSURL urlWithString:#"http://localhost:1234/swap"];
#
# -[SPAuth handleAuthCallbackWithTriggeredAuthURL:url
# tokenSwapServiceEndpointAtURL:swapServiceURL
# callback:callback];
#
print "\e[31m------------------------------------------------------\e[0m\n"
print "\e[31mYou're using example credentials, please replace these\e[0m\n"
print "\e[31mwith your own and remove this silly warning.\e[0m\n"
print "\e[31m------------------------------------------------------\e[0m\n"
print "\7\7"
sleep(2)
CLIENT_ID = ""
CLIENT_SECRET = ""
ENCRYPTION_SECRET = ""
CLIENT_CALLBACK_URL = "dawgone://returnhere"
AUTH_HEADER = "Basic " + Base64.strict_encode64(CLIENT_ID + ":" + CLIENT_SECRET)
SPOTIFY_ACCOUNTS_ENDPOINT = URI.parse("https://accounts.spotify.com")
set :port, 1234 # The port to bind to.
set :bind, '0.0.0.0' # IP address of the interface to listen on (all)
post '/swap' do
# This call takes a single POST parameter, "code", which
# it combines with your client ID, secret and callback
# URL to get an OAuth token from the Spotify Auth Service,
# which it will pass back to the caller in a JSON payload.
auth_code = params[:code]
http = Net::HTTP.new(SPOTIFY_ACCOUNTS_ENDPOINT.host, SPOTIFY_ACCOUNTS_ENDPOINT.port)
http.use_ssl = true
request = Net::HTTP::Post.new("/api/token")
request.add_field("Authorization", AUTH_HEADER)
request.form_data = {
"grant_type" => "authorization_code",
"redirect_uri" => CLIENT_CALLBACK_URL,
"code" => auth_code
}
response = http.request(request)
# encrypt the refresh token before forwarding to the client
if response.code.to_i == 200
token_data = JSON.parse(response.body)
refresh_token = token_data["refresh_token"]
encrypted_token = refresh_token.encrypt(:symmetric, :password => ENCRYPTION_SECRET)
token_data["refresh_token"] = encrypted_token
response.body = JSON.dump(token_data)
end
status response.code.to_i
return response.body
end
post '/refresh' do
# Request a new access token using the POST:ed refresh token
http = Net::HTTP.new(SPOTIFY_ACCOUNTS_ENDPOINT.host, SPOTIFY_ACCOUNTS_ENDPOINT.port)
http.use_ssl = true
request = Net::HTTP::Post.new("/api/token")
request.add_field("Authorization", AUTH_HEADER)
encrypted_token = params[:refresh_token]
refresh_token = encrypted_token.decrypt(:symmetric, :password => ENCRYPTION_SECRET)
request.form_data = {
"grant_type" => "refresh_token",
"refresh_token" => refresh_token
}
response = http.request(request)
status response.code.to_i
return response.body
end

This is because swap is a POST endpoint. When you pull up a URL in your browser you are doing an HTTP GET.
If you want to see that the sinatra service is running and you can at least talk to it you could try hitting it with curl from the command line with the right POST parameters.

Related

Getting authorization from iNaturalist for API

I'm trying to use iNaturalist's API via Ruby on Rails. I'm new to Ruby and iNaturalist's documentation is pretty sparse. As a first step, I need to figure out how to get authorization from their site.
iNaturalist provides the sample code below. I set up a project with iNaturalist and tried running the sample code in Rails Console with my credentials. #{url} in the following line is replaced with a url that the user is supposed to go to in order to log in to iNat:
puts "Go to #{url}, approve the app, and you should be redirected to your " +
"redirect_uri. Copy and paste the 'code' param here."
I went to the resulting url and logged in:
https://www.inaturalist.org/oauth/authorize?client_id=[my client id]&redirect_uri=https://ruby_on_rails--timrobinson41199691.codeanyapp.com/login/&response_type=code
iNaturalist responds with "The redirect uri included is not valid."
If I leave off &response_type=code, it responds with "The authorization server does not support this response type."
My website is on codeanywhere.com. The url of the main page is "https://ruby_on_rails--timrobinson41199691.codeanyapp.com/". Part of the problem is that I don't understand what kind of page I'm supposed to create for redirect_uri, since I'm still kind of new at this.
require 'rubygems'
require 'rest_client'
require 'json'
site = "https://www.inaturalist.org"
app_id = 'YOUR APP ID'
app_secret = 'YOUR APP SECRET'
redirect_uri = 'YOUR APP REDIRECT URI' # you can set this to some URL you control for testing
# REQUEST AN AUTHORIZATION CODE
# Your web app should redirect the user to this url. They should see a screen
# offering them the choice to authorize your app. If they aggree, they will be
# redirected to your redirect_uri with a "code" parameter
url = "#{site}/oauth/authorize?client_id=#{app_id}&redirect_uri=#{redirect_uri}&response_type=code"
# REQUEST AN AUTH TOKEN
# Once your app has that code parameter, you can exchange it for an access token:
puts "Go to #{url}, approve the app, and you should be redirected to your " +
"redirect_uri. Copy and paste the 'code' param here."
print "Code: "
auth_code = gets.strip
puts
payload = {
:client_id => app_id,
:client_secret => app_secret,
:code => auth_code,
:redirect_uri => redirect_uri,
:grant_type => "authorization_code"
}
puts "POST #{site}/oauth/token, payload: #{payload.inspect}"
puts response = RestClient.post("#{site}/oauth/token", payload)
puts
# response will be a chunk of JSON looking like
# {
# "access_token":"xxx",
# "token_type":"bearer",
# "expires_in":null,
# "refresh_token":null,
# "scope":"write"
# }
# Store the token (access_token) in your web app. You can now use it to make authorized
# requests on behalf of the user, like retrieving profile data:
token = JSON.parse(response)["access_token"]
headers = {"Authorization" => "Bearer #{token}"}
puts "GET /users/edit.json, headers: #{headers.inspect}"
puts RestClient.get("#{site}/users/edit.json", headers)
puts
After the user logs in to iNat, he should be redirected back to my website with the authorization code provided in the data. In routes.rb, my login route is set as:
post '/login', to: 'organisms#login'
I've tried using get, as well.
iNat is returned the error mentioned above and not redirecting back to my site.
OAuth can be a bit daunting at first. And that guide really just shows the equivalent of using cURL to test your API.
In an actual application redirect_uri is whatever endpoint in your application that handles the response when the provider redirects back from authorization.
So lets setup a minimal real rails app.
1. Register your app
Register a new application or edit your existing app.
Use http://localhost:3000/oauth/inaturalist/callback for the callback url (adjust the host as needed).
Keep the window open as you will need the client_id and secret in a moment.
2. Setup your routes
# /config/routes.rb
Rails.application.routes.draw do
# just make sure you have a root path defined.
root to: 'pages#home'
namespace :oauth do
namespace :inaturalist, controller: :callbacks do
# This is just a simple redirect route
get '/', action: :passthru, as: :authorize
# This is the route that handles the actual callback
get 'callback'
end
end
end
You can actually do this without the redirect route and just plant a link to the https://www.inaturalist.org/oauth/authorize... url in your view. But having it isolates your application against the craziness that is OAuth and its how OmniAuth does it.
3. Add your credentials to the Rails app.
In Rails 5 use the encrypted credentials to store your client_id and secret.
Run $ bin/rails credentials:edit from your shell.
inaturalist:
client_id: <from the inaturalist site>
secret: <from the inaturalist site>
In earlier versions use ENV vars instead.
4. Install the oauth2 gem
# Place this in your gemfile outside any groups
gem 'oauth2', '~> 1.4', '>= 1.4.1'
Then run bundle install.
4. Setup the controller
# app/controllers/oauth/inaturalist/callbacks_controller.rb
require 'oauth2'
module Oauth
module Inaturalist
class CallbacksController < ::ActionController::Base
# GET /oauth/inaturalist
def passthru
redirect_to client.auth_code.authorize_url
end
# This endpoint is where the provider redirects the user back to
# after authorization.
# GET /oauth/inaturalist/callback
def callback
# Fetch an auth token from the access code
token = client.auth_code.get_token(params[:code])
# Perform an authenticated request to get the users data
api_response = token.get("/users/edit.json")
#user_data = JSON.parse(api_response.body)
# This is just an example of how you can use the user data from
# the provider
#user = {
uid: #user_data["id"],
nickname: #user_data["nickname"]
}
session[:user_id] = #user[:uid]
session[:token] = token.to_hash
redirect_to root_path, success: "Hello #{#user[:nickname]}"
end
private
# Change this if you are not using Rails 5 credentials.
def client
OAuth2::Client.new(
credentials.fetch(:client_id),
credentials.fetch(:secret),
site: "https://www.inaturalist.org",
redirect_uri: oauth_inaturalist_callback_url
)
end
def credentials
Rails.application.credentials.fetch(:inaturalist)
end
end
end
end
token here is actually a new OAuth2::AccessToken instance that can be called to call endpoints with the fetched credentials.
This example stores the token in the session. You can retrieve it in subsequent requests with:
token = OAuth2::AccessToken.from_hash( session[:token] )
The docs kind of mention trading the oauth access token for an api token for api.inaturalist.org. But the details are kind of sparse.
5 Add a link to sign in:
<%= link_to 'Sign in to iNaturalist.org', oauth_inaturalist_authorize_path %>

How safe is it to use an ip for running a Sinatra ruby app for an iOS application

I am building an iOS app and I have an AWS ec2 instance running a Sinatra app for refreshing and swapping a Spotify SDK access token and I am wondering about any safety issues with having a url such as http://#someIP:4567 on the application itself.
I know with an AWS ec2 instance you can get it secured by making it a https but how can you secure an IP in the same sense(If I even need to do so)?
Here is what is in the ruby file:
require 'sinatra'
require 'net/http'
require 'net/https'
require 'base64'
require 'encrypted_strings'
require 'json'
set :bind, '0.0.0.0'
CLIENT_ID = ENV['TheClientIDGivenBySpotify']
CLIENT_SECRET = ENV['TheClientSecretGivenBySpotify']
ENCRYPTION_SECRET = ENV['cFJLyifeUJUBFWdHzVbykfDmPHtLKLGzViHW9aHGmyTLD8hGXC']
CLIENT_CALLBACK_URL = ENV['appForSpotify://returnAfterLogin']
SPOTIFY_ACCOUNTS_ENDPOINT = URI.parse("https://accounts.spotify.com")
get '/' do
"Working"
end
post '/swap' do
AUTH_HEADER = "Basic " + Base64.strict_encode64(CLIENT_ID + ":" + CLIENT_SECRET)
# This call takes a single POST parameter, "code", which
# it combines with your client ID, secret and callback
# URL to get an OAuth token from the Spotify Auth Service,
# which it will pass back to the caller in a JSON payload.
auth_code = params[:code]
http = Net::HTTP.new(SPOTIFY_ACCOUNTS_ENDPOINT.host, SPOTIFY_ACCOUNTS_ENDPOINT.port)
http.use_ssl = true
request = Net::HTTP::Post.new("/api/token")
request.add_field("Authorization", AUTH_HEADER)
request.form_data = {
"grant_type" => "authorization_code",
"redirect_uri" => CLIENT_CALLBACK_URL,
"code" => auth_code
}
response = http.request(request)
# encrypt the refresh token before forwarding to the client
if response.code.to_i == 200
token_data = JSON.parse(response.body)
refresh_token = token_data["refresh_token"]
encrypted_token = refresh_token.encrypt(:symmetric, :password => ENCRYPTION_SECRET)
token_data["refresh_token"] = encrypted_token
response.body = JSON.dump(token_data)
end
status response.code.to_i
return response.body
end
post '/refresh' do
AUTH_HEADER = "Basic " + Base64.strict_encode64(CLIENT_ID + ":" + CLIENT_SECRET)
# Request a new access token using the POST:ed refresh token
http = Net::HTTP.new(SPOTIFY_ACCOUNTS_ENDPOINT.host, SPOTIFY_ACCOUNTS_ENDPOINT.port)
http.use_ssl = true
request = Net::HTTP::Post.new("/api/token")
request.add_field("Authorization", AUTH_HEADER)
encrypted_token = params[:refresh_token]
refresh_token = encrypted_token.decrypt(:symmetric, :password => ENCRYPTION_SECRET)
request.form_data = {
"grant_type" => "refresh_token",
"refresh_token" => refresh_token
}
response = http.request(request)
status response.code.to_i
return response.body
end
In xcode I would POST to http://#someIP:4567/swap or http://#someIP:4567/refresh
Is this safe to do?
Am I handling this correctly?
By having the request being sent to an IP that can be accessible by anyone, am I putting myself and anyone else who is using the application in danger of having their information stolen or viewed?
HTTPS is only available for domain names. Not available for IP. If you only use IP, then you are exposed to MITM attack and all your data is plain text flying around. Anyone can eavesdrop your request. You have a code parameter in the swap request and I guess is code is credential correct? If yes, it's better to grab some domain name and set it up using aws route53. Then buy some cheap SSL certificate to associate it with the domain. Then on the iOS side, enable HTTPS instead of HTTP to communicate with your server. This has nothing to do with your Sinatra app.
To know more about SSL/HTTPS, you can take a look at some beginner tutorials like this: http://www.hongkiat.com/blog/ssl-certs-guide/

How to access data requested by Post method in rails

Am trying to access data from a certain API using POST method but it returns back the actual list of params that I sent. Here is my code I don't know whether am doing this right, I will be glad for your help.
This is my controller
#Request access token from ExactApi
params = {
"code" => "#{code}",
"redirect_uri" => '/auth/exact/callback',
"grant_type" => "authorization_code",
"client_id" => "{CLIENT_ID}",
"client_secret" => "CLIENT_SECRET"
}
uri = URI.parse('https://start.exactonline.nl/api/oauth2/token')
#Encode the url into /x-www-form-urlencoded
uri.query = URI.encode_www_form(params)
#Transform http protocol into a secure protocol[https]
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE # You should use VERIFY_PEER in production
#Send the request to the ExactApi and return the received data
res = Net::HTTP::Post.new(uri.request_uri)
res.set_form_data(params)
puts "Received:: "+ res.body.to_yaml
Output
code[CODE]&redirect_uri=%2Fauth%2Fexact%2Fcallback&grant_type=authorization_code&client_id=CLIENT_ID&client_secret=SECRET_ID
How can I access the actual data returned from API?
require "net/http"
require "uri"
uri = URI.parse('https://start.exactonline.nl/api/oauth2/token')
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Post.new(uri.request_uri)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE # You should use VERIFY_PEER in production
request.set_form_data({
"code" => "#{code}",
"redirect_uri" => '/auth/exact/callback',
"grant_type" => "authorization_code",
"client_id" => CLIENT_ID,
"client_secret" => CLIENT_SECRET
})
response = http.request(request)
http://www.rubyinside.com/nethttp-cheat-sheet-2940.html
However I would use Omniauth instead of reinventing the Oauth wheel. Its pretty hard to get right. If you cannot find a ready made provider then creating a custom provider is pretty simple:
require 'omniauth-oauth2'
module OmniAuth
module Strategies
class ExactOnline < OmniAuth::Strategies::OAuth2
# change the class name and the :name option to match your application name
option :name, :exactonile
option :client_options, {
:site => "https://start.exactonline.nl",
:authorize_url => "/api/oauth2/token"
}
uid { raw_info["id"] }
info do
{
:email => raw_info["email"]
# and anything else you want to return to your API consumers
}
end
def raw_info
#raw_info ||= access_token.get('/api/v1/me.json').parsed
end
end
end
end
You are using puts which outputs to the server console. I'm confused where the output is. You should set yourself up a view with the same name as the controller action your block of code is within, for example if this is the index action:
def index
params= { your_hash_keys: "value" }
end
Then you should have an index.html.erb inside the app/views/controller_name/ In your controller instead of puts "Received:: "+ res.body.to_yaml use #debug = "Received:: "+ res.body.to_yaml and inside your view do something to output it like <%= #debug.inspect %>
Alternatively, and not recommended is to render inline in the controller:
render inline: "Received:: " + res.body.to_yaml
Layouts and Rendering with inline
You also should rename your params variable, since that is used by Rails for the incoming parameters. All in all I think a tutorial on MVC would be a good place to start.

Problems understanding a Ruby example file for Google API Client Authentication

I am trying to access Google APIs from my Rails App.
I understand the basic flow for OAuth2.0 for Web Applications.
And I consider myself having a basic understanding of Ruby and Rails.
Yet, I can't make sense of the example Google shows in the "API Client Library for Ruby (Alpha)" Guides.
require 'google/apis/calendar_v3'
require 'google/api_client/client_secrets'
require 'sinatra'
require 'logger'
enable :sessions
def logger; settings.logger end
def calendar; settings.calendar; end
def user_credentials
# Build a per-request oauth credential based on token stored in session
# which allows us to use a shared API client.
#authorization ||= (
auth = settings.authorization.dup
auth.redirect_uri = to('/oauth2callback')
auth.update_token!(session)
auth
)
end
configure do
log_file = File.open('calendar.log', 'a+')
log_file.sync = true
logger = Logger.new(log_file)
logger.level = Logger::DEBUG
Google::Apis::ClientOptions.default.application_name = 'Ruby Calendar sample'
Google::Apis::ClientOptions.default.application_version = '1.0.0'
calendar_api = Google::Apis::CalendarV3::CalendarService.new
client_secrets = Google::APIClient::ClientSecrets.load
authorization = client_secrets.to_authorization
authorization.scope = 'https://www.googleapis.com/auth/calendar'
set :authorization, authorization
set :logger, logger
set :calendar, calendar_api
end
before do
# Ensure user has authorized the app
unless user_credentials.access_token || request.path_info =~ /^\/oauth2/
redirect to('/oauth2authorize')
end
end
after do
# Serialize the access/refresh token to the session and credential store.
session[:access_token] = user_credentials.access_token
session[:refresh_token] = user_credentials.refresh_token
session[:expires_in] = user_credentials.expires_in
session[:issued_at] = user_credentials.issued_at
end
get '/oauth2authorize' do
# Request authorization
redirect user_credentials.authorization_uri.to_s, 303
end
get '/oauth2callback' do
# Exchange token
user_credentials.code = params[:code] if params[:code]
user_credentials.fetch_access_token!
redirect to('/')
end
get '/' do
# Fetch list of events on the user's default calandar
events = calendar.list_events('primary', options: { authorization: user_credentials })
[200, {'Content-Type' => 'application/json'}, events.to_h.to_json]
end
Of course this looks like Ruby, but I never saw some of the constructs used here, and I am not sure where in a Rails app that code would go.
Is it Ruby? If so, how do I use it?

authentication using google spreadsheet api

https://developers.google.com/gdata/articles/using_ruby
I'm following the "Authentication | Using the Google Spreadsheets API" section in above tutorial.
rb(main):008:0> require 'net/https'
=> true
irb(main):009:0> http = Net::HTTP.new('www.google.com', 443)
=> #<Net::HTTP www.google.com:443 open=false>
irb(main):010:0> http.use_ssl = true
=> true
irb(main):011:0> path = '/accounts/ClientLogin'
=> "/accounts/ClientLogin"
# Now we are passing in our actual authentication data.
# Please visit OAuth For Installed Apps for more information
# about the accountType parameter
irb(main):014:0> data = \
irb(main):015:0* 'accountType=HOSTED_OR_GOOGLE&Email=your email' \
irb(main):016:0* '&Passwd=your password' \
irb(main):017:0* '&service=wise'
irb(main):018:0> headers = \
irb(main):019:0* { 'Content-Type' => 'application/x-www-form-urlencoded'}
=> {"Content-Type"=>"
application/x-www-form-urlencoded"}
# Post the request and print out the response to retrieve our authentication token
irb(main):020:0> resp, data = http.post(path, data, headers)
=> #<Net::HTTPLengthRequired 411 Length Required readbody=true>
I'm supposed to get "=> [#, "SID=DQAAAIIAAADgV7j4F-QVQjnxdDRjpslHKC3M ... [ snipping out the rest of the authentication strings ]" after the POST. I put my gmail id and password in email and password.
What is problem?
It's possible that they removed the service today. When I request this page https://www.google.com/accounts/ClientLogin I receive 404 (not found).

Resources