school_friend 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ **0.1.0 (June 28, 2013)**
2
+ * Add OAuth2 support ([Daniel Chcouri](https://github.com/theosp))
3
+
4
+ **0.0.1 (December 3, 2012)** (initial release)
5
+ * Create gem skeleton
data/README.md CHANGED
@@ -17,9 +17,38 @@ Or install it yourself as:
17
17
  $ gem install school_friend
18
18
 
19
19
  ## Usage
20
-
21
- TODO: Write usage instructions here
22
-
20
+ ```ruby
21
+ # Init the SchoolFriend Module
22
+ SchoolFriend.application_id = ''
23
+ SchoolFriend.application_key = ''
24
+ SchoolFriend.secret_key = ''
25
+ SchoolFriend.api_server = 'http://api.odnoklassniki.ru'
26
+
27
+ # Example call to a method that doesn't require a session or oauth2 access token
28
+ puts SchoolFriend.users.is_app_user(:uid => "425634635") # Note that method name is underscored
29
+
30
+ # Init an Oauth2 Session
31
+ # You can init an oauth session in two ways:
32
+ # 1. By providing the oauth code and let this gem to acquire the access token for you
33
+ session = SchoolFriend.session(:oauth_code => code)
34
+
35
+ # 2. By providing an access token and a refresh token you already have
36
+ session = SchoolFriend.session(:access_token => access_token, :refresh_token => refresh_token)
37
+
38
+ # 3. Refreshing the access token:
39
+ # According to Odnoklassniki's API doc access token is valid for 30 minutes
40
+ # You can extend this time by refreshing the access token this will give
41
+ # you a new token that will be valid for 30 days
42
+ # http://dev.odnoklassniki.ru/wiki/pages/viewpage.action?pageId=12878032
43
+ session.refresh_access_token
44
+
45
+ # Alternative - non oauth: Acquire a session with user's user_name and password (not recommanded)
46
+ # Odnoklassniki's sandbox has no oauth2 support so you have to use this method there
47
+ session = SchoolFriend.session(SchoolFriend.auth.login(:user_name => username, :password => password))
48
+
49
+ # Once you have a session you can perform actions with it:
50
+ puts session.users.get_current_user
51
+ ```
23
52
  ## Contributing
24
53
 
25
54
  1. Fork it
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'logger'
4
+ require 'yaml'
5
+ require 'school_friend'
6
+ require 'time'
7
+ require 'socket'
8
+
9
+ log = Logger.new(STDOUT)
10
+ log.level = Logger::DEBUG
11
+
12
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
13
+
14
+ example_settings = YAML.load_file(File.dirname(__FILE__) + '/examples_settings.yaml')
15
+
16
+ puts "Login using Odnoklassniki: \"#{example_settings['oauth_server']}/oauth/authorize?client_id=#{example_settings['application_id']}&scope=#{example_settings['permission']}&response_type=code&redirect_uri=http://127.0.0.1:#{example_settings['listener_port']}\""
17
+
18
+ log.debug "Initiating server to catch the oauth code"
19
+ server = TCPServer.new(example_settings['listener_port'])
20
+ loop {
21
+ log.debug "Server initiated"
22
+
23
+ client = server.accept
24
+ headers = "HTTP/1.1 200 OK\r\nDate: #{Time.now.httpdate}\r\nServer: Ruby\r\nContent-Type: text/html; charset=iso-8859-1\r\n\r\n"
25
+
26
+ lines = ""
27
+ while line = client.gets and line !~ /^\s*$/
28
+ lines << line.chomp
29
+ end
30
+
31
+ if match = /code=(\S*)/.match(lines)
32
+ code = match[1]
33
+ log.debug "Oauth code received: #{code}"
34
+
35
+ # Init SchoolFriend module class vars
36
+ SchoolFriend.application_id = example_settings['application_id']
37
+ SchoolFriend.application_key = example_settings['application_key']
38
+ SchoolFriend.secret_key = example_settings['secret_key']
39
+ SchoolFriend.api_server = example_settings['api_server']
40
+ log.debug "SchoolFriend module class variables initiated: #{SchoolFriend.application_id} #{SchoolFriend.application_key} #{SchoolFriend.secret_key} #{SchoolFriend.api_server} "
41
+
42
+ # Set SchoolFriend's logger
43
+ SchoolFriend.logger = log
44
+
45
+ begin
46
+ # Example for oauth_code based init
47
+ session = SchoolFriend.session(:oauth_code => code)
48
+
49
+ puts session.users.is_app_user
50
+ puts session.users.get_current_user
51
+ puts session.users.get_info
52
+
53
+ # Example for access_token based init
54
+ session = SchoolFriend.session(:access_token => session.options[:access_token], :refresh_token => session.options[:refresh_token])
55
+ puts session.users.is_app_user
56
+ puts session.users.get_current_user
57
+ puts session.users.get_info
58
+ rescue SchoolFriend::Session::OauthCodeAuthenticationFailedError
59
+ puts "Failed to authenticate oauth code, try again in a few moments..."
60
+ end
61
+ end
62
+
63
+ client.puts headers
64
+ client.puts "OK"
65
+ client.close
66
+ }
@@ -0,0 +1,10 @@
1
+ # Your app settings
2
+ oauth_server: https://www.odnoklassniki.ru
3
+ api_server: http://api.odnoklassniki.ru
4
+ application_id: 1
5
+ application_key: C
6
+ secret_key: C
7
+ permission: SET STATUS;VALUABLE ACCESS;PHOTO CONTENT
8
+
9
+ # Options related to the TCPServer we open to receive the oauth code
10
+ listener_port: 2000
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'logger'
4
+ require 'yaml'
5
+ require 'school_friend'
6
+ require 'time'
7
+ require 'socket'
8
+
9
+ log = Logger.new(STDOUT)
10
+ log.level = Logger::INFO
11
+
12
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
13
+
14
+ example_settings = YAML.load_file(File.dirname(__FILE__) + '/examples_settings.yaml')
15
+
16
+ puts "Login using Odnoklassniki: \"#{example_settings['oauth_server']}/oauth/authorize?client_id=#{example_settings['application_id']}&scope=#{example_settings['permission']}&response_type=code&redirect_uri=http://127.0.0.1:#{example_settings['listener_port']}\""
17
+
18
+ def operation_pretty_print title, result
19
+ puts "*" * 80
20
+ puts title
21
+ puts "*" * 80
22
+
23
+ puts ""
24
+ begin
25
+ puts JSON.pretty_generate(result)
26
+ rescue JSON::GeneratorError
27
+ puts result
28
+ end
29
+ end
30
+
31
+ log.debug "Initiating server to catch the oauth code"
32
+ server = TCPServer.new(example_settings['listener_port'])
33
+ loop {
34
+ log.debug "Server initiated"
35
+
36
+ client = server.accept
37
+ headers = "HTTP/1.1 200 OK\r\nDate: #{Time.now.httpdate}\r\nServer: Ruby\r\nContent-Type: text/html; charset=iso-8859-1\r\n\r\n"
38
+
39
+ lines = ""
40
+ while line = client.gets and line !~ /^\s*$/
41
+ lines << line.chomp
42
+ end
43
+
44
+ if match = /code=(\S*)/.match(lines)
45
+ code = match[1]
46
+ log.debug "Oauth code received: #{code}"
47
+
48
+ # Init SchoolFriend module class vars
49
+ SchoolFriend.application_id = example_settings['application_id']
50
+ SchoolFriend.application_key = example_settings['application_key']
51
+ SchoolFriend.secret_key = example_settings['secret_key']
52
+ SchoolFriend.api_server = example_settings['api_server']
53
+ log.debug "SchoolFriend module class variables initiated: #{SchoolFriend.application_id} #{SchoolFriend.application_key} #{SchoolFriend.secret_key} #{SchoolFriend.api_server} "
54
+
55
+ # Set SchoolFriend's logger
56
+ SchoolFriend.logger = log
57
+
58
+ begin
59
+ # Example for oauth_code based init
60
+ session = SchoolFriend.session(:oauth_code => code)
61
+ session.refresh_access_token
62
+
63
+ current_user = session.users.get_current_user
64
+ operation_pretty_print "users.get_current_user", current_user
65
+ operation_pretty_print "friends.get_app_users (Returns IDs of users who are friends of the current user and authorize the calling application)", session.friends.get_app_users
66
+ operation_pretty_print "friends.get", friends = session.friends.get
67
+
68
+ operation_pretty_print "users.get_info", session.users.get_info(:uids => friends.join(","), :fields => "uid,first_name,last_name,name,gender,birthday,age,locale,location,current_location,current_status,current_status_id,current_status_date,online,pic_1,pic_2,pic_3,pic_4,pic_5,url_profile,url_profile_mobile,url_chat,url_chat_mobile,has_email,allows_anonym_access")
69
+
70
+ operation_pretty_print "discussions.get_list", session.discussions.get_list
71
+
72
+ status = "Cool Status #{Random.rand(0...1000)}"
73
+ operation_pretty_print "users.set_status - #{status}", session.users.set_status(:status => status)
74
+
75
+ message = "Cool Message #{Random.rand(0...1000)}"
76
+ operation_pretty_print "share.add_link - #{message}", session.share.add_link(:comment => message, :linkUrl => "http://www.imdb.com/title/tt0110413/")
77
+ message = "Cool Message #{Random.rand(0...1000)}"
78
+ operation_pretty_print "share.add_link - #{message}", session.share.add_link(:comment => message, :linkUrl => "http://www.imdb.com/title/tt0137523/")
79
+ message = "Cool Message #{Random.rand(0...1000)}"
80
+ operation_pretty_print "share.add_link - #{message}", session.share.add_link(:comment => message, :linkUrl => "http://www.youtube.com/watch?v=wxaFANthouM")
81
+
82
+ operation_pretty_print "users.get_calls_left", session.users.get_calls_left(:methods => "users.getInfo,stream.publish")
83
+ rescue SchoolFriend::Session::OauthCodeAuthenticationFailedError
84
+ puts "Failed to authenticate oauth code, try again in a few moments..."
85
+ end
86
+ end
87
+
88
+ client.puts headers
89
+ client.puts "OK"
90
+ client.close
91
+ }
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'logger'
4
+ require 'yaml'
5
+ require 'school_friend'
6
+ require 'time'
7
+ require 'socket'
8
+
9
+ log = Logger.new(STDOUT)
10
+ log.level = Logger::DEBUG
11
+
12
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
13
+
14
+ example_settings = YAML.load_file(File.dirname(__FILE__) + '/examples_settings.yaml')
15
+
16
+ puts "Login using Odnoklassniki: \"#{example_settings['oauth_server']}/oauth/authorize?client_id=#{example_settings['application_id']}&scope=#{example_settings['permission']}&response_type=code&redirect_uri=http://127.0.0.1:#{example_settings['listener_port']}\""
17
+
18
+ log.debug "Initiating server to catch the oauth code"
19
+ server = TCPServer.new(example_settings['listener_port'])
20
+ loop {
21
+ log.debug "Server initiated"
22
+
23
+ client = server.accept
24
+ headers = "HTTP/1.1 200 OK\r\nDate: #{Time.now.httpdate}\r\nServer: Ruby\r\nContent-Type: text/html; charset=iso-8859-1\r\n\r\n"
25
+
26
+ lines = ""
27
+ while line = client.gets and line !~ /^\s*$/
28
+ lines << line.chomp
29
+ end
30
+
31
+ if match = /code=(\S*)/.match(lines)
32
+ code = match[1]
33
+ log.debug "Oauth code received: #{code}"
34
+
35
+ # Init SchoolFriend module class vars
36
+ SchoolFriend.application_id = example_settings['application_id']
37
+ SchoolFriend.application_key = example_settings['application_key']
38
+ SchoolFriend.secret_key = example_settings['secret_key']
39
+ SchoolFriend.api_server = example_settings['api_server']
40
+ log.debug "SchoolFriend module class variables initiated: #{SchoolFriend.application_id} #{SchoolFriend.application_key} #{SchoolFriend.secret_key} #{SchoolFriend.api_server} "
41
+
42
+ # Set SchoolFriend's logger
43
+ SchoolFriend.logger = log
44
+
45
+ begin
46
+ # Example for oauth_code based init
47
+ session = SchoolFriend.session(:oauth_code => code)
48
+ session.refresh_access_token
49
+ session.refresh_access_token
50
+ puts session.users.get_current_user
51
+ rescue SchoolFriend::Session::OauthCodeAuthenticationFailedError
52
+ puts "Failed to authenticate oauth code, try again in a few moments..."
53
+ end
54
+ end
55
+
56
+ client.puts headers
57
+ client.puts "OK"
58
+ client.close
59
+ }
@@ -0,0 +1,9 @@
1
+ # Running Examples
2
+
3
+ First set examples_settings.yaml params.
4
+
5
+ In order to run an example you just have to execute it, example:
6
+
7
+ ./basic_auth.rb
8
+
9
+ and follow its instructions.
data/lib/school_friend.rb CHANGED
@@ -6,7 +6,7 @@ module SchoolFriend
6
6
  class << self
7
7
  extend Forwardable
8
8
 
9
- attr_accessor :application_key, :secret_key, :api_server
9
+ attr_accessor :application_id, :application_key, :secret_key, :api_server, :logger
10
10
 
11
11
  def_delegators :session, *REST_NAMESPACES
12
12
 
@@ -14,6 +14,10 @@ module SchoolFriend
14
14
  Session.new(options)
15
15
  end
16
16
  end
17
+
18
+ self.logger = Logger.new(STDOUT)
19
+ self.logger.level = Logger::WARN
20
+
17
21
  end
18
22
 
19
23
  require 'school_friend/version'
@@ -32,4 +36,4 @@ require 'school_friend/rest/photos_v2'
32
36
  require 'school_friend/rest/share'
33
37
  require 'school_friend/rest/stream'
34
38
  require 'school_friend/rest/users'
35
- require 'school_friend/rest/widget'
39
+ require 'school_friend/rest/widget'
@@ -19,14 +19,14 @@ module SchoolFriend
19
19
  call += options[:session_only] ? ', true)' : ')'
20
20
 
21
21
  class_eval <<-OES, __FILE__, __LINE__ + 1
22
- def #{method}(params = {}) # def get_widgets(params = {})
23
- response = #{call} # response = session.api_call('widget.getWidgets', params, true)
24
- if response.is_a?(Net::HTTPSuccess) # if response.is_a?(Net::HTTPSuccess)
25
- JSON(response.body) # JSON(response.body)
26
- else # else
27
- response.error! # response.error!
28
- end # end
29
- end # end
22
+ def #{method}(params = {}) # def get_widgets(params = {})
23
+ response = #{call} # response = session.api_call('widget.getWidgets', params, true)
24
+ if response.is_a?(Net::HTTPSuccess) # if response.is_a?(Net::HTTPSuccess)
25
+ JSON.parse(response.body, :quirks_mode => true) # JSON(response.body, :quirks_mode => true)
26
+ else # else
27
+ response.error! # response.error!
28
+ end # end
29
+ end # end
30
30
  OES
31
31
  end
32
32
  end
@@ -3,6 +3,7 @@ module SchoolFriend
3
3
  class Discussions
4
4
  include APIMethods
5
5
 
6
+ api_method :get_list, session_only: true
6
7
  api_method :get_discussions, session_only: true
7
8
  api_method :get_discussion_comments_count, session_only: true
8
9
  api_method :add_discussion_comment, session_only: true
@@ -1,16 +1,101 @@
1
1
  require 'net/http'
2
2
  require 'digest'
3
+ require 'json'
4
+
5
+ def keys_to_symbols! (_hash)
6
+ _hash.keys.each do |key|
7
+ _hash[(key.to_sym rescue key) || key] = _hash.delete(key)
8
+ end
9
+
10
+ return _hash
11
+ end
3
12
 
4
13
  module SchoolFriend
5
14
  class Session
6
15
  class RequireSessionScopeError < ArgumentError
7
16
  end
8
17
 
18
+ class OauthCodeAuthenticationFailedError < StandardError
19
+ end
20
+
9
21
  attr_reader :options, :session_scope
10
22
 
23
+ def _post_request(url, params)
24
+ uri = URI.parse(url)
25
+ http = Net::HTTP.new(uri.host, 80)
26
+ data = URI.encode_www_form(params)
27
+
28
+ headers = {
29
+ 'Content-Type' => 'application/x-www-form-urlencoded'
30
+ }
31
+
32
+ http.post(uri.path, data, headers)
33
+ end
34
+
11
35
  def initialize(options = {})
12
- @options = options
13
- @session_scope = options[:session_key] && options[:session_secret_key]
36
+ @options = keys_to_symbols!(options)
37
+ @session_scope = (options[:session_key] && options[:session_secret_key]) || \
38
+ options[:oauth_code] || \
39
+ (options[:access_token] && options[:refresh_token])
40
+
41
+ # only has oauth_code, get access_token
42
+ if options[:oauth_code]
43
+ response, data = \
44
+ _post_request(api_server + "/oauth/token.do",
45
+ {"code" => options[:oauth_code], "redirect_uri" => "http://127.0.0.1:2000",
46
+ "client_id" => SchoolFriend.application_id, "client_secret" => SchoolFriend.secret_key,
47
+ "grant_type" => 'authorization_code'})
48
+
49
+ if response.is_a?(Net::HTTPSuccess)
50
+ response = JSON(response.body)
51
+
52
+ if response.has_key?("error")
53
+ raise OauthCodeAuthenticationFailedError, "failed to use oauth_code for authentication: #{response["error"]}: #{response["error_description"]}"
54
+ end
55
+
56
+ options[:access_token] = response["access_token"]
57
+ options[:refresh_token] = response["refresh_token"]
58
+
59
+ SchoolFriend.logger.debug "Tokens received: #{options[:access_token]} #{options[:refresh_token]}"
60
+ else
61
+ raise OauthCodeAuthenticationFailedError, "failed to use oauth_code for authentication - Request Failed"
62
+ end
63
+
64
+ options.delete(:oauth_code)
65
+ end
66
+ end
67
+
68
+ def refresh_access_token
69
+ # true on success false otherwise
70
+ if oauth2_session?
71
+ response, data = \
72
+ _post_request(api_server + "/oauth/token.do",
73
+ {"refresh_token" => options[:refresh_token],\
74
+ "client_id" => SchoolFriend.application_id, "client_secret" => SchoolFriend.secret_key,
75
+ "grant_type" => 'refresh_token'})
76
+
77
+ if response.is_a?(Net::HTTPSuccess)
78
+ response = JSON(response.body)
79
+
80
+ if response.has_key?("error")
81
+ SchoolFriend.logger.warn "#{__method__}: failed to refresh access token - #{response["error"]}"
82
+
83
+ return false
84
+ end
85
+
86
+ options[:access_token] = response["access_token"]
87
+
88
+ SchoolFriend.logger.debug "#{__method__}: Token received: #{options[:access_token]}"
89
+
90
+ return true
91
+ el
92
+ SchoolFriend.logger.warn "#{__method__}: Failed to refresh access token - request Failed"
93
+
94
+ return false
95
+ end
96
+ else
97
+ return false
98
+ end
14
99
  end
15
100
 
16
101
  # Returns true if API call is performed in session scope
@@ -25,6 +110,10 @@ module SchoolFriend
25
110
  not session_scope?
26
111
  end
27
112
 
113
+ def oauth2_session?
114
+ options[:access_token] && options[:refresh_token]
115
+ end
116
+
28
117
  # Returns application key
29
118
  #
30
119
  # @return [String]
@@ -36,7 +125,11 @@ module SchoolFriend
36
125
  #
37
126
  # @return [String]
38
127
  def signature
39
- @signature ||= session_scope? ? options[:session_secret_key] : SchoolFriend.secret_key
128
+ unless session_scope?
129
+ return SchoolFriend.secret_key
130
+ end
131
+
132
+ options[:access_token] || options[:session_secret_key]
40
133
  end
41
134
 
42
135
  # Returns API server
@@ -53,7 +146,14 @@ module SchoolFriend
53
146
  def sign(params = {})
54
147
  params = additional_params.merge(params)
55
148
  digest = params.sort_by(&:first).map{ |key, value| "#{key}=#{value}" }.join
56
- params[:sig] = Digest::MD5.hexdigest("#{digest}#{signature}")
149
+
150
+ if oauth2_session?
151
+ params[:sig] = Digest::MD5.hexdigest("#{digest}" + Digest::MD5.hexdigest(options[:access_token] + SchoolFriend.secret_key))
152
+ params[:access_token] = options[:access_token]
153
+ else
154
+ params[:sig] = Digest::MD5.hexdigest("#{digest}#{signature}")
155
+ end
156
+
57
157
  params
58
158
  end
59
159
 
@@ -63,7 +163,11 @@ module SchoolFriend
63
163
  # @return [Hash]
64
164
  def additional_params
65
165
  @additional_params ||= if session_scope?
66
- {application_key: application_key, session_key: options[:session_key]}
166
+ if oauth2_session?
167
+ {application_key: application_key}
168
+ else
169
+ {application_key: application_key, session_key: options[:session_key]}
170
+ end
67
171
  else
68
172
  {application_key: application_key}
69
173
  end
@@ -100,6 +204,9 @@ module SchoolFriend
100
204
  uri = URI(api_server)
101
205
  uri.path = '/api/' + method.sub('.', '/')
102
206
  uri.query = URI.encode_www_form(sign(params))
207
+
208
+ SchoolFriend.logger.debug "API Request: #{uri}"
209
+
103
210
  uri
104
211
  end
105
212
 
@@ -111,4 +218,4 @@ module SchoolFriend
111
218
  EOS
112
219
  end
113
220
  end
114
- end
221
+ end
@@ -1,3 +1,3 @@
1
1
  module SchoolFriend
2
- VERSION = '0.0.1'
2
+ VERSION = '0.1.0'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: school_friend
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-03 00:00:00.000000000 Z
12
+ date: 2013-06-28 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: A Ruby interface to the Odnoklassniki API
15
15
  email: kostya.stepanyuk@gmail.com
@@ -18,10 +18,16 @@ extensions: []
18
18
  extra_rdoc_files: []
19
19
  files:
20
20
  - .gitignore
21
+ - CHANGELOG.md
21
22
  - Gemfile
22
23
  - LICENSE.txt
23
24
  - README.md
24
25
  - Rakefile
26
+ - examples/basic_auth.rb
27
+ - examples/examples_settings.yaml
28
+ - examples/operations.rb
29
+ - examples/refresh_token.rb
30
+ - examples/using_these_examples.md
25
31
  - lib/school_friend.rb
26
32
  - lib/school_friend/api_methods.rb
27
33
  - lib/school_friend/rest/auth.rb
@@ -61,7 +67,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
61
67
  version: '0'
62
68
  requirements: []
63
69
  rubyforge_project:
64
- rubygems_version: 1.8.24
70
+ rubygems_version: 1.8.25
65
71
  signing_key:
66
72
  specification_version: 3
67
73
  summary: A Ruby interface to the Odnoklassniki API