Created
October 24, 2011 23:10
-
-
Save basicxman/1310680 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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