school_friend 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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