Skip to content

Instantly share code, notes, and snippets.

@donfrancisco
Forked from basicxman/google_calendar.rb
Created July 30, 2012 10:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save donfrancisco/3206012 to your computer and use it in GitHub Desktop.
Save donfrancisco/3206012 to your computer and use it in GitHub Desktop.
# Google Calendar access w/ Data API
# OAuth authentication.
require "sinatra"
require "excon"
require "cgi"
require "base64"
require "openssl"
require "digest/hmac"
require "json/pure"
class GoogleCalendar
attr_accessor :request_token, :request_secret, :authorized_request_token, :authorized_request_verifier, :access_token, :access_secret
def initialize(key, secret, callback)
@key = key
@secret = secret
@callback = callback
@base_url = "https://www.google.com"
@scope = @base_url + "/calendar/feeds"
@request_token_url = "/accounts/OAuthGetRequestToken"
@authorize_token_url = "/accounts/OAuthAuthorizeToken"
@access_token_url = "/accounts/OAuthGetAccessToken"
@feeds_url = @base_url + "/calendar/feeds/default/owncalendars/full"
end
# Initial OAuth flow, get an unauthorized request token from Google and then
# pass to the user for authorization.
def execute_flow
get_request_token
authorize_token
end
# http://code.google.com/apis/calendar/data/2.0/developers_guide_protocol.html#RetrievingAllCalendars
# Sample authorized GET request.
def get_feeds
api_call(@feeds_url).body
end
# http://code.google.com/apis/calendar/data/2.0/developers_guide_protocol.html#CreatingCalendars
# Sample authorized POST request.
def create_calendar
data = {
:title => "Testing testing!",
:details => "The answer is 42.",
:timeZone => "America/New_York",
:hidden => false,
:color => "#A32929",
:location => "New York"
}
body = { :data => data }.to_json
api_call(@feeds_url, :post, body).body
end
# User has authorized with Google and we have a token from the callback.
def from_authorization(params)
@authorized_request_token = params["oauth_token"]
@authorized_request_verifier = params["oauth_verifier"]
get_access_token
end
private
# Standard request for authorized API calls.
def api_call(url, method = :get, data = "")
authentication, params = generate_authentication_hash({ :oauth_token => @access_token }), { :alt => "jsonc" }
authentication.merge! oauth_signature(secret_string(@secret, @access_secret), method, url, authentication, params)
headers = {
"Authorization" => authorization_string(authentication),
"GData-Version" => "2", # http://code.google.com/apis/calendar/data/2.0/developers_guide_protocol.html#Versioning
"Accept" => "application/json"
}
headers["Content-Type"] = "application/json" if method == :post
request(method, url + "?" + normalize_parameters(params), data, headers)
end
# http://code.google.com/apis/accounts/docs/OAuth_ref.html#RequestToken
def get_request_token
url = @base_url + @request_token_url
authentication, params = generate_authentication_hash({ :oauth_callback => @callback + "/oauth/authorize" }), { :scope => @scope }
authentication.merge! oauth_signature(secret_string(@secret), :post, url, authentication, params)
response = request(:post, url, normalize_parameters(params), { "Authorization" => authorization_string(authentication) })
tokens = parse_tokens(response.body)
@request_token = tokens[:oauth_token]
@request_secret = tokens[:oauth_token_secret]
end
# http://code.google.com/apis/accounts/docs/OAuth_ref.html#GetAuth
def authorize_token
url = @base_url + @authorize_token_url + "?" + normalize_parameters({
:oauth_token => @request_token,
:hd => "default",
:hl => "en"
})
`open "#{url}"`
end
# http://code.google.com/apis/accounts/docs/OAuth_ref.html#AccessToken
def get_access_token
url = @base_url + @access_token_url
authentication = generate_authentication_hash({
:oauth_token => @authorized_request_token,
:oauth_verifier => @authorized_request_verifier
})
authentication.merge! oauth_signature(secret_string(@secret, @request_secret), :post, url, authentication)
response = request(:post, url, "", { "Authorization" => authorization_string(authentication) })
tokens = parse_tokens(response.body)
@access_token = tokens[:oauth_token]
@access_secret = tokens[:oauth_token_secret]
end
# Even if the token secret is blank, the ampersand is required.
# http://oauth.net/core/1.0/#rfc.section.9.2
def secret_string(secret, token_secret = "")
"#{secret}&#{token_secret}"
end
def request(method, url, body, headers = {})
hash = if method == :post
{
"Accept" => "*/*",
"Content-Type" => "application/x-www-form-urlencoded"
}
else
{}
end
Excon.send(method, url, :body => body, :headers => hash.merge(headers)) # Hit that server yo.
end
def parse_tokens(string)
string.split("&").inject({}) do |hash, pair|
key, value = pair.split("=")
hash.merge({ key.to_sym => CGI.unescape(value) })
end
end
# Set of mandatory request-independent OAuth authentication parameters.
def generate_authentication_hash(hash = {})
{
:oauth_consumer_key => @key,
:oauth_nonce => nonce,
:oauth_signature_method => "HMAC-SHA1",
:oauth_timestamp => timestamp,
:oauth_version => "1.0"
}.merge(hash)
end
# Generate a signature parameter hash with a signed signature base.
# http://code.google.com/apis/accounts/docs/OAuth_ref.html#SigningOAuth
def oauth_signature(secret, method, url, authentication, params = {})
{ :oauth_signature => sign(secret, generate_signature_base(method, url, normalize_parameters(authentication.merge(params)))) }
end
# http://oauth.net/core/1.0/#rfc.section.9.1.3
def generate_signature_base(method, url, param_string)
[method.to_s.upcase, CGI.escape(url), CGI.escape(param_string)].join("&")
end
# Using the HMAC-SHA1 signature method.
def sign(secret, string)
Base64.encode64(OpenSSL::HMAC.digest("sha1", secret, string)).strip
end
# Generates string for the authorization header.
def authorization_string(params)
"OAuth " + params.sort.map { |key, value| "#{key}=\"#{CGI.escape(value)}\"" }.join(", ")
end
# Normalized parameters for signature or query string according to OAuth spec.
# http://oauth.net/core/1.0/#rfc.section.9.1.1
def normalize_parameters(params)
params.sort.inject("") { |str, (key, value)| str + "#{CGI.escape(key.to_s)}=#{CGI.escape(value)}&" }[0..-2]
end
def nonce
Digest::MD5.hexdigest(rand.to_s)
end
def timestamp
Time.now.to_i.to_s
end
end
get "/oauth/authorize" do
$calendar.from_authorization(params)
redirect "/oauth/info"
end
get "/oauth/info" do
<<-eof
Request Token: #{$calendar.request_token}<br />
Request Secret: #{$calendar.request_secret}<br />
Authorized Request Token: #{$calendar.authorized_request_token}<br />
Authorized Request Verifier: #{$calendar.authorized_request_verifier}<br />
Access Token: #{$calendar.access_token}<br />
Access Secret: #{$calendar.access_secret}<br />
<br />
#{$calendar.get_feeds}
<br />
<br />
#{$calendar.create_calendar}
eof
end
$calendar = GoogleCalendar.new("anonymous", "anonymous", "http://localhost:4567")
$calendar.execute_flow
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment