opensocial 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,117 @@
1
+ # Copyright (c) 2008 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ module OpenSocial #:nodoc:
17
+
18
+ # Acts as a wrapper for an OpenSocial appdata entry.
19
+ #
20
+ # The AppData class takes a person's ID and input JSON as initialization
21
+ # parameters, and iterates through each of the key/value pairs of that JSON.
22
+ # For each key that is found, an attr_accessor is constructed (except for
23
+ # :id which is preconstructed, and required), allowing direct access to the
24
+ # value. Each value is stored in the attr_accessor, either as a String,
25
+ # Fixnum, Hash, or Array.
26
+ #
27
+
28
+
29
+ class AppData < Base
30
+ attr_accessor :id
31
+
32
+ # Initializes the AppData entry based on the provided id and json fragment.
33
+ # If no JSON is provided, an empty object (with only an ID) is created.
34
+ def initialize(id, json)
35
+ @id = id
36
+
37
+ if json
38
+ json.each do |key, value|
39
+ begin
40
+ self.send("#{key}=", value)
41
+ rescue NoMethodError
42
+ add_attr(key)
43
+ self.send("#{key}=", value)
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ # Provides the ability to request a Collection of AppData for a given
51
+ # user or set of users.
52
+ #
53
+ # The FetchAppData wraps a simple request to an OpenSocial
54
+ # endpoint for a Collection of AppData. As parameters, it accepts
55
+ # a user ID and selector. This request may be used, standalone, by calling
56
+ # send, or bundled into an RpcRequest.
57
+ #
58
+
59
+
60
+ class FetchAppDataRequest < Request
61
+ # Defines the service fragment for use in constructing the request URL or
62
+ # JSON
63
+ SERVICE = 'appdata'
64
+
65
+ # Initializes a request to fetch appdata for the specified user and
66
+ # group, or the default (@me, @self). A valid Connection is not necessary
67
+ # if the request is to be used as part of an RpcRequest.
68
+ def initialize(connection = nil, guid = '@me', selector = '@self',
69
+ aid = '@app')
70
+ super(connection, guid, selector, aid)
71
+ end
72
+
73
+ # Sends the request, passing in the appropriate SERVICE and specified
74
+ # instance variables. Accepts an unescape parameter, defaulting to true,
75
+ # if the returned data should be unescaped.
76
+ def send(unescape = true)
77
+ json = send_request(SERVICE, @guid, @selector, @pid, unescape)
78
+
79
+ return parse_response(json['entry'])
80
+ end
81
+
82
+ # Selects the appropriate fragment from the JSON response in order to
83
+ # create a native object.
84
+ def parse_rpc_response(response)
85
+ return parse_response(response['data'])
86
+ end
87
+
88
+ # Converts the request into a JSON fragment that can be used as part of a
89
+ # larger RpcRequest.
90
+ def to_json(*a)
91
+ value = {
92
+ 'method' => SERVICE + GET,
93
+ 'params' => {
94
+ 'userId' => ["#{@guid}"],
95
+ 'groupId' => "#{@selector}",
96
+ 'appId' => "#{@pid}",
97
+ 'fields' => []
98
+ },
99
+ 'id' => @key
100
+ }.to_json(*a)
101
+ end
102
+
103
+ private
104
+
105
+ # Converts the JSON response into a Collection of AppData entries, indexed
106
+ # by id.
107
+ def parse_response(response)
108
+ appdata = Collection.new
109
+ response.each do |key, value|
110
+ data = AppData.new(key, value)
111
+ appdata[key] = data
112
+ end
113
+
114
+ return appdata
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,87 @@
1
+ # Copyright (c) 2007 Blaine Cook, Larry Halff, Pelle Braendgaard
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+ #
22
+ # Includes modifications by Robin Luckey from:
23
+ # http://github.com/robinluckey/oauth/tree/master/lib%2Foauth%2Frequest_proxy%2Faction_controller_request.rb
24
+
25
+ require 'rubygems'
26
+ require 'active_support'
27
+ require 'oauth/request_proxy/action_controller_request'
28
+ require 'uri'
29
+
30
+ module OAuth::RequestProxy #:nodoc: all
31
+ class ActionControllerRequest < OAuth::RequestProxy::Base
32
+ proxies ActionController::AbstractRequest
33
+
34
+ def method
35
+ request.method.to_s.upcase
36
+ end
37
+
38
+ def uri
39
+ uri = URI.parse(request.protocol + request.host + request.port_string + request.path)
40
+ uri.query = nil
41
+ uri.to_s
42
+ end
43
+
44
+ def parameters
45
+ if options[:clobber_request]
46
+ options[:parameters] || {}
47
+ else
48
+ params = request_params.merge(query_params).merge(header_params)
49
+ params.stringify_keys! if params.respond_to?(:stringify_keys!)
50
+ params.merge(options[:parameters] || {})
51
+ end
52
+ end
53
+
54
+ # Override from OAuth::RequestProxy::Base to avoid roundtrip
55
+ # conversion to Hash or Array and thus preserve the original
56
+ # parameter names
57
+ def parameters_for_signature
58
+ params = []
59
+ params << options[:parameters].to_query if options[:parameters]
60
+
61
+ unless options[:clobber_request]
62
+ params << header_params.to_query
63
+ params << CGI.unescape(request.query_string) unless request.query_string.blank?
64
+ if request.content_type == Mime::Type.lookup("application/x-www-form-urlencoded")
65
+ params << CGI.unescape(request.raw_post)
66
+ end
67
+ end
68
+
69
+ params.
70
+ join('&').split('&').
71
+ reject { |kv| kv =~ /^oauth_signature=.*/}.
72
+ reject(&:blank?).
73
+ map { |p| p.split('=') }
74
+ end
75
+
76
+ protected
77
+
78
+ def query_params
79
+ request.query_parameters
80
+ end
81
+
82
+ def request_params
83
+ request.request_parameters
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,114 @@
1
+ # Copyright (c) 2008 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'rubygems'
16
+ require 'oauth'
17
+ require 'oauth/consumer'
18
+
19
+
20
+ module OpenSocial #:nodoc:
21
+
22
+ # Provides helper classes to be used in verifying and validating the user.
23
+ # In particular, support is provided for:
24
+ #
25
+ # * Verification of signed makeRequest using OAuth/HMAC-SHA1
26
+ # class ExampleController < ApplicationController
27
+ # OpenSocial::Auth::CONSUMER_KEY = '623061448914'
28
+ # OpenSocial::Auth::CONSUMER_SECRET = 'uynAeXiWTisflWX99KU1D2q5'
29
+ #
30
+ # include OpenSocial::Auth
31
+ #
32
+ # before_filter :validate
33
+ #
34
+ # def return_private_data
35
+ # end
36
+ # end
37
+ #
38
+ # * Request for an OAuth request token
39
+ #
40
+ # * Request for an OAuth access token, when supplied with a request token
41
+ #
42
+
43
+
44
+ module Auth
45
+
46
+ # Validates an incoming request by using the OAuth library and the supplied
47
+ # key and secret.
48
+ def validate(key = CONSUMER_KEY, secret = CONSUMER_SECRET)
49
+ consumer = OAuth::Consumer.new(key, secret)
50
+ begin
51
+ signature = OAuth::Signature.build(request) do
52
+ [nil, consumer.secret]
53
+ end
54
+ pass = signature.verify
55
+ rescue OAuth::Signature::UnknownSignatureMethod => e
56
+ logger.error 'An unknown signature method was supplied: ' + e.to_s
57
+ end
58
+ return pass
59
+ end
60
+
61
+ # Gets an OAuth request token, and redirects the user to authorize the app
62
+ # to access data on their behalf.
63
+ def get_oauth_token(key, secret, container, callback)
64
+ consumer = OAuth::Consumer.new(key, secret, {
65
+ :site => container[:base_uri],
66
+ :request_token_path => container[:request_token_path],
67
+ :authorize_path => container[:authorize_path],
68
+ :access_token_path => container[:access_token_path],
69
+ :http_method => container[:http_method]
70
+ })
71
+ request_token = consumer.get_request_token
72
+
73
+ session[:token] = request_token.token
74
+ session[:secret] = request_token.secret
75
+
76
+ redirect_to request_token.authorize_url + '&oauth_callback=' + CGI.escape(callback)
77
+ end
78
+
79
+ # If neccesary, swaps an existing request token and secret for an access
80
+ # token, storing it in the Connection class, and returning the access token
81
+ # and secret for later use.
82
+ def get_access_token(connection, token, secret)
83
+ if (token && secret)
84
+ consumer = OAuth::Consumer.new(connection.consumer_key,
85
+ connection.consumer_secret,
86
+ connection.container)
87
+
88
+ if connection.consumer_token.token.empty? &&
89
+ connection.consumer_token.secret.empty?
90
+ connection.consumer_token = OAuth::Token.new(token, secret)
91
+
92
+ uri = URI.parse(connection.container[:base_uri] +
93
+ connection.container[:access_token_path])
94
+ http = Net::HTTP.new(uri.host, uri.port)
95
+ req = Net::HTTP::Get.new(uri.request_uri)
96
+ connection.sign!(http, req)
97
+
98
+ resp = http.get(req.path)
99
+
100
+ matches = resp.body.match(/oauth_token=(.*?)&oauth_token_secret=(.*)/)
101
+ access_token = matches[1]
102
+ access_secret = matches[2]
103
+ end
104
+
105
+ reusable_token = OAuth::AccessToken.new(consumer, access_token, access_secret)
106
+ connection.consumer_token = reusable_token
107
+
108
+ return access_token, access_secret
109
+ end
110
+
111
+ return nil, nil
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,54 @@
1
+ # Copyright (c) 2008 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'net/http'
16
+ require 'uri'
17
+
18
+ # Use json/pure if you opt-out of including the validation code in
19
+ # opensocial/auth. This gem adds standard to_json behavior for the
20
+ # request classes, instead of using ActiveSupport.
21
+ require 'rubygems'
22
+ require 'json/add/rails'
23
+
24
+
25
+ module OpenSocial #:nodoc:
26
+
27
+ # Provides base functionality for the OpenSocial child classes.
28
+ #
29
+
30
+
31
+ class Base
32
+
33
+ # Creates an attr_accessor for the specified variable name.
34
+ def add_attr(name)
35
+ self.class.class_eval "attr_accessor :#{name}"
36
+ end
37
+ end
38
+
39
+ # Wraps the functionality of an OpenSocial collection as defined by the
40
+ # specification. In practical uses, a Collection serves as a Hash (usually
41
+ # index by ID) with the added benefit of being convertable to an Array, when
42
+ # it's necessary to iterate over all of the values.
43
+ #
44
+
45
+
46
+ class Collection < Hash
47
+
48
+ # Converts the Collection to an Array by returning each of the values from
49
+ # key/value pairs.
50
+ def to_array
51
+ values
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,146 @@
1
+ # Copyright (c) 2008 Google Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'oauth/consumer'
16
+
17
+
18
+ module OpenSocial #:nodoc:
19
+
20
+ # Describes a connection to an OpenSocial container, including the ability to
21
+ # declare an authorization mechanism and appropriate credentials.
22
+ #
23
+
24
+
25
+ class Connection
26
+ ORKUT = { :endpoint => 'http://sandbox.orkut.com/social',
27
+ :rest => 'rest/',
28
+ :rpc => 'rpc/',
29
+ :content_type => 'application/json',
30
+ :post_body_signing => false,
31
+ :use_request_body_hash => true }
32
+ IGOOGLE = { :endpoint => 'http://www-opensocial-sandbox.googleusercontent.com/api',
33
+ :rest => '',
34
+ :rpc => 'rpc',
35
+ :content_type => 'application/json',
36
+ :post_body_signing => false,
37
+ :use_request_body_hash => true }
38
+ MYSPACE = { :endpoint => 'http://api.myspace.com/v2',
39
+ :rest => '',
40
+ :rpc => '',
41
+ :base_uri => 'http://api.myspace.com',
42
+ :request_token_path => '/request_token',
43
+ :authorize_path => '/authorize',
44
+ :access_token_path => '/access_token',
45
+ :http_method => :get,
46
+ :content_type => 'application/x-www-form-urlencoded',
47
+ :post_body_signing => true,
48
+ :use_request_body_hash => false }
49
+
50
+ AUTH_HMAC = 0
51
+ AUTH_ST = 1
52
+
53
+ DEFAULT_OPTIONS = { :container => ORKUT,
54
+ :st => '',
55
+ :consumer_key => '',
56
+ :consumer_secret => '',
57
+ :consumer_token => OAuth::Token.new('', ''),
58
+ :xoauth_requestor_id => '',
59
+ :auth => AUTH_HMAC }
60
+
61
+ # Defines the container that will be used in requests.
62
+ attr_accessor :container
63
+
64
+ # Defines the security token, for when OAuth is not in use.
65
+ attr_accessor :st
66
+
67
+ # Defines the consumer key for OAuth.
68
+ attr_accessor :consumer_key
69
+
70
+ # Defines the consumer secret for OAuth.
71
+ attr_accessor :consumer_secret
72
+
73
+ # Defines the consumer token for OAuth.
74
+ attr_accessor :consumer_token
75
+
76
+ # Defines the ID of the requestor (required by some implementations when
77
+ # using OAuth).
78
+ attr_accessor :xoauth_requestor_id
79
+
80
+ # Defines the authentication scheme: HMAC or security token.
81
+ attr_accessor :auth
82
+
83
+ # Defines the content-type when sending a request body
84
+ attr_accessor :content_type
85
+
86
+ # Defines whether or not to sign the request body (treating the body as a
87
+ # large query parameter for the purposes of the signature base string)
88
+ attr_accessor :post_body_signing
89
+
90
+ # Defines whether or not to sign the body using a request body hash
91
+ attr_accessor :use_request_body_hash
92
+
93
+ # Initializes the Connection using the supplied options hash, or the
94
+ # defaults. Verifies that the supplied authentication type has proper
95
+ # (ie. non-blank) credentials, and that the authentication type is known.
96
+ def initialize(options = {})
97
+ options = DEFAULT_OPTIONS.merge(options)
98
+ options.each do |key, value|
99
+ self.send("#{key}=", value)
100
+ end
101
+
102
+ if @auth == AUTH_HMAC && !has_valid_hmac_double?
103
+ raise ArgumentError.new('Connection authentication is set to ' +
104
+ 'HMAC-SHA1, but a valid consumer_key and' +
105
+ 'secret pair was not supplied.')
106
+ elsif @auth == AUTH_ST && @st.empty?
107
+ raise ArgumentError.new('Connection authentication is set to ' +
108
+ 'security token, but a security token was ' +
109
+ 'not supplied.')
110
+ elsif ![AUTH_HMAC, AUTH_ST].include?(@auth)
111
+ raise ArgumentError.new('Connection authentication is set to an ' +
112
+ 'unknown value.')
113
+ end
114
+ end
115
+
116
+ # Constructs a URI to the OpenSocial endpoint given a service, guid,
117
+ # selector, and pid.
118
+ def service_uri(service, guid, selector, pid)
119
+ uri = [@container[:endpoint], service, guid, selector, pid].compact.
120
+ join('/')
121
+
122
+ if @auth == AUTH_HMAC && !xoauth_requestor_id.empty?
123
+ uri << '?xoauth_requestor_id=' + @xoauth_requestor_id
124
+ elsif @auth == AUTH_ST
125
+ uri << '?st=' + self.st
126
+ end
127
+ URI.parse(uri)
128
+ end
129
+
130
+ # Signs a request using OAuth.
131
+ def sign!(http, req)
132
+ if @auth == AUTH_HMAC
133
+ consumer = OAuth::Consumer.new(@consumer_key, @consumer_secret)
134
+ req.oauth!(http, consumer, nil, :scheme => 'query_string')
135
+ end
136
+ end
137
+
138
+ private
139
+
140
+ # Verifies that the consumer key, consumer secret and requestor id are all
141
+ # non-blank.
142
+ def has_valid_hmac_double?
143
+ return (!@consumer_key.empty? && !@consumer_secret.empty?)
144
+ end
145
+ end
146
+ end