uservoice-ruby 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ /spec/config.yml
6
+ *.sw*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in uservoice.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,119 @@
1
+ UserVoice gem for API connections
2
+ =================================
3
+
4
+ This gem allows you to easily:
5
+ * Generate SSO token for creating SSO users / logging them into UserVoice (http://uservoice.com).
6
+ * Do 3-legged and 2-legged UserVoice API calls safely without having to worry about the cryptographic details.
7
+
8
+ Installation
9
+ ============
10
+
11
+ Place this in your Gemfile:
12
+ ```ruby
13
+ gem 'uservoice', :git => 'https://github.com/uservoice/uservoice-ruby'
14
+ ```
15
+ Run the bundle command and then try one of the examples below.
16
+
17
+ Examples
18
+ ========
19
+
20
+ Prerequisites:
21
+ * Suppose your UserVoice site is at http://uservoice-subdomain.uservoice.com/ and **USERVOICE\_SUBDOMAIN** = uservoice-subdomain
22
+ * **SSO\_KEY** = 982c88f2df72572859e8e23423eg87ed (Admin Console -> Settings -> General -> User Authentication)
23
+ * The account has a following API client (Admin Console -> Settings -> Channels -> API):
24
+ * **API\_KEY** = oQt2BaunWNuainc8BvZpAm
25
+ * **API\_SECRET** = 3yQMSoXBpAwuK3nYHR0wpY6opE341inL9a2HynGF2
26
+
27
+
28
+ SSO-token generation using uservoice gem
29
+ ----------------------------------------
30
+
31
+ SSO-token can be used to create sessions for SSO users. They are capable of synchronizing the user information from one system to another.
32
+ Generating the SSO token from SSO key and given uservoice subdomain can be done by calling UserVoice.generate\_sso\_token method like this:
33
+
34
+ ```ruby
35
+ require 'uservoice'
36
+ sso_token = UserVoice.generate_sso_token(USERVOICE_SUBDOMAIN, SSO_KEY, {
37
+ :guid => 1001,
38
+ :display_name => "John Doe",
39
+ :email => 'john.doe@example.com'
40
+ })
41
+
42
+ # Now this URL will log John Doe in:
43
+ puts "https://#{USERVOICE_SUBDOMAIN}.uservoice.com/?sso=#{sso_token}"
44
+ ```
45
+
46
+ Making API calls
47
+ ----------------
48
+
49
+ With the gem you need to create an instance of UserVoice::Oauth. You get
50
+ API_KEY and API_SECRET from an API client which you can create in Admin Console
51
+ -> Settings -> Channels -> API.
52
+
53
+ ```ruby
54
+ require 'uservoice'
55
+ begin
56
+ uservoice_client = UserVoice::Client.new(USERVOICE_SUBDOMAIN, API_KEY, API_SECRET)
57
+
58
+ # Get users of a subdomain (requires trusted client, but no user)
59
+ users = uservoice_client.get("/api/v1/users.json?per_page=3")['users']
60
+ users.each do |user|
61
+ puts "User: \"#{user['name']}\", Profile URL: #{user['url']}"
62
+ end
63
+
64
+ # Now, let's login as mailaddress@example.com, a regular user
65
+ uservoice_client.login_as('mailaddress@example.com')
66
+
67
+ # Example request #1: Get current user.
68
+ user = uservoice_client.get("/api/v1/users/current.json")['user']
69
+
70
+ puts "User: \"#{user['name']}\", Profile URL: #{user['url']}"
71
+
72
+ # Login as account owner
73
+ uservoice_client.login_as_owner
74
+
75
+ # Example request #2: Create a new private forum limited to only example.com email domain.
76
+ forum = uservoice_client.post("/api/v1/forums.json", :forum => {
77
+ :name => 'Example.com Private Feedback',
78
+ :private => true,
79
+ :allow_by_email_domain => true,
80
+ :allowed_email_domains => [{:domain => 'example.com'}]
81
+ })['forum']
82
+
83
+ puts "Forum '#{forum['name']}' created! URL: #{forum['url']}"
84
+ rescue UserVoice::Unauthorized => e
85
+ # Thrown usually due to faulty tokens, untrusted client or if attempting
86
+ # operations without Admin Privileges
87
+ raise
88
+ end
89
+ ```
90
+
91
+ Verifying a UserVoice user
92
+ --------------------------
93
+
94
+ If you want to make calls on behalf of a user, but want to make sure he or she
95
+ actually owns certain email address in UserVoice, you need to use 3-Legged API
96
+ calls. Just pass your user an authorize link to click, so that user may grant
97
+ your site permission to access his or her data in UserVoice.
98
+
99
+ ```ruby
100
+ require 'uservoice'
101
+ CALLBACK_URL = 'http://localhost:3000/' # your site
102
+
103
+ uservoice_client = UserVoice::Client.new(USERVOICE_SUBDOMAIN, API_KEY, API_SECRET, :callback => CALLBACK_URL)
104
+
105
+ # At this point you want to print/redirect to uservoice_client.authorize_url in your application.
106
+ # Here we just output them as this is a command-line example.
107
+ puts "1. Go to #{uservoice_client.authorize_url} and click \"Allow access\"."
108
+ puts "2. Then type the oauth_verifier which is passed as a GET parameter to the callback URL:"
109
+
110
+ # In a web app we would get the oauth_verifier through a redirect from UserVoice (after a redirection back to CALLBACK_URL).
111
+ # In this command-line example we just read it from stdin:
112
+ uservoice_client.login_verified_user(gets.match('\w*').to_s)
113
+
114
+ # All done. Now we can read the current user to know user's email address:
115
+ user = uservoice_client.get("/api/v1/users/current.json")['user']
116
+
117
+ puts "User logged in, Name: #{user['name']}, email: #{user['email']}"
118
+ ```
119
+
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ task :spec do
5
+ RSpec::Core::RakeTask.new(:spec) do |t|
6
+ t.pattern = './spec/**/*_spec.rb'
7
+ end
8
+ end
@@ -0,0 +1,160 @@
1
+ require "uservoice/version"
2
+ require 'rubygems'
3
+ require 'ezcrypto'
4
+ require 'json'
5
+ require 'cgi'
6
+ require 'base64'
7
+ require 'oauth'
8
+
9
+ module UserVoice
10
+ EMAIL_FORMAT = %r{^(\w[-+.\w!\#\$%&'\*\+\-/=\?\^_`\{\|\}~]*@([-\w]*\.)+[a-zA-Z]{2,9})$}
11
+ DEFAULT_HEADERS = { 'Content-Type'=> 'application/json', 'Accept'=> 'application/json' }
12
+
13
+ class APIError < RuntimeError
14
+ end
15
+ Unauthorized = Class.new(APIError)
16
+ NotFound = Class.new(APIError)
17
+ ApplicationError = Class.new(APIError)
18
+
19
+ def self.generate_sso_token(subdomain_key, sso_key, user_hash, valid_for = 5 * 60)
20
+ user_hash[:expires] ||= (Time.now.utc + valid_for).to_s unless valid_for.nil?
21
+ unless user_hash[:email].to_s.match(EMAIL_FORMAT)
22
+ raise Unauthorized.new("'#{user_hash[:email]}' is not a valid email address")
23
+ end
24
+ unless sso_key.to_s.length > 1
25
+ raise Unauthorized.new("Please specify your SSO key")
26
+ end
27
+
28
+ key = EzCrypto::Key.with_password(subdomain_key, sso_key)
29
+ encrypted = key.encrypt(user_hash.to_json)
30
+ encoded = Base64.encode64(encrypted).gsub(/\n/,'')
31
+
32
+ return CGI.escape(encoded)
33
+ end
34
+
35
+ class Client
36
+
37
+ def initialize(*args)
38
+ case args.size
39
+ when 3,4
40
+ init_subdomain_and_api_keys(*args)
41
+ when 1,2
42
+ init_consumer_and_access_token(*args)
43
+ end
44
+ end
45
+
46
+ def init_subdomain_and_api_keys(subdomain_name, api_key, api_secret, attrs={})
47
+ consumer = OAuth::Consumer.new(api_key, api_secret, {
48
+ :site => "#{attrs[:protocol] || 'https'}://#{subdomain_name}.#{attrs[:uservoice_domain] || 'uservoice.com'}"
49
+ })
50
+ init_consumer_and_access_token(consumer, attrs)
51
+ end
52
+
53
+ def init_consumer_and_access_token(consumer, attrs={})
54
+ @consumer = consumer
55
+ @token = OAuth::AccessToken.new(@consumer, attrs[:oauth_token] || '', attrs[:oauth_token_secret] || '')
56
+ @response_format = attrs[:response_format] || :hash
57
+ @callback = attrs[:callback]
58
+ end
59
+
60
+ def authorize_url
61
+ request_token.authorize_url
62
+ end
63
+
64
+ def login_with_verifier(oauth_verifier)
65
+ token = request_token.get_access_token(:oauth_verifier => oauth_verifier)
66
+ Client.new(@consumer, :oauth_token => token.token, :oauth_token_secret => token.secret)
67
+ end
68
+
69
+ def login_with_access_token(oauth_token, oauth_token_secret, &block)
70
+ token = Client.new(@consumer, :oauth_token => oauth_token, :oauth_token_secret => oauth_token_secret)
71
+ if block_given?
72
+ yield token
73
+ else
74
+ return token
75
+ end
76
+ end
77
+
78
+ def token
79
+ @token.token
80
+ end
81
+
82
+ def secret
83
+ @token.secret
84
+ end
85
+
86
+ def request_token
87
+ @consumer.get_request_token(:oauth_callback => @callback)
88
+ end
89
+
90
+ def login_as_owner(&block)
91
+ token = post('/api/v1/users/login_as_owner.json', {
92
+ 'request_token' => request_token.token
93
+ })['token']
94
+ if token
95
+ login_with_access_token(token['oauth_token'], token['oauth_token_secret'], &block)
96
+ else
97
+ raise Unauthorized.new("Could not get Access Token")
98
+ end
99
+ end
100
+
101
+ def login_as(email, &block)
102
+ unless email.to_s.match(EMAIL_FORMAT)
103
+ raise Unauthorized.new("'#{email}' is not a valid email address")
104
+ end
105
+ token = post('/api/v1/users/login_as.json', {
106
+ :user => { :email => email },
107
+ :request_token => request_token.token
108
+ })['token']
109
+
110
+ if token
111
+ login_with_access_token(token['oauth_token'], token['oauth_token_secret'], &block)
112
+ else
113
+ raise Unauthorized.new("Could not get Access Token")
114
+ end
115
+ end
116
+
117
+ def request(method, uri, request_body={}, headers={})
118
+ headers = DEFAULT_HEADERS.merge(headers)
119
+
120
+ if headers['Content-Type'] == 'application/json' && request_body.is_a?(Hash)
121
+ request_body = request_body.to_json
122
+ end
123
+
124
+ response = case method.to_sym
125
+ when :post, :put
126
+ @token.request(method, uri, request_body, headers)
127
+ when :head, :delete, :get
128
+ @token.request(method, uri, headers)
129
+ else
130
+ raise RuntimeError.new("Invalid HTTP method #{method}")
131
+ end
132
+
133
+ return case @response_format.to_s
134
+ when 'raw'
135
+ response
136
+ else
137
+ attrs = JSON.parse(response.body)
138
+ if attrs && attrs['errors']
139
+ case attrs['errors']['type']
140
+ when 'unauthorized'
141
+ raise Unauthorized.new(attrs)
142
+ when 'record_not_found'
143
+ raise NotFound.new(attrs)
144
+ when 'application_error'
145
+ raise ApplicationError.new(attrs)
146
+ else
147
+ raise APIError.new(attrs)
148
+ end
149
+ end
150
+ attrs
151
+ end
152
+ end
153
+
154
+ %w(get post delete put).each do |method|
155
+ define_method(method) do |*args|
156
+ request(method, *args)
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,3 @@
1
+ module Uservoice
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1 @@
1
+ require 'uservoice/user_voice'
@@ -0,0 +1,4 @@
1
+ subdomain_name: uservoice
2
+ api_key: GIOIuigIGuisguiGisg3Gd
3
+ api_secret: GIIGDSDUGI678sGoOrGvGJSGISKSDGTEWUfhGoW9Gu
4
+ sso_key: 678bfe76feb876feb786fbe876feb67e
@@ -0,0 +1,186 @@
1
+ require 'spec_helper'
2
+
3
+ describe UserVoice do
4
+
5
+ it "should generate SSO token" do
6
+ token = UserVoice.generate_sso_token(config['subdomain_name'], config['sso_key'], {
7
+ :display_name => "User Name",
8
+ :email => 'mailaddress@example.com'
9
+ })
10
+ encrypted_raw_data = Base64.decode64(CGI.unescape(token))
11
+
12
+ key = EzCrypto::Key.with_password(config['subdomain_name'], config['sso_key'])
13
+ key.decrypt(encrypted_raw_data).should match('mailaddress@example.com')
14
+ end
15
+
16
+ describe UserVoice::Client do
17
+ subject { UserVoice::Client.new(config['subdomain_name'],
18
+ config['api_key'],
19
+ config['api_secret'],
20
+ :uservoice_domain => config['uservoice_domain'],
21
+ :protocol => config['protocol']) }
22
+
23
+ it "should get user names from the API" do
24
+ users = subject.get("/api/v1/users.json?per_page=3")
25
+ user_names = users['users'].map { |user| user['name'] }
26
+ user_names.all?.should == true
27
+ user_names.size.should == 3
28
+ end
29
+
30
+ it "should not get current user without logged in user" do
31
+ lambda do
32
+ user = subject.get("/api/v1/users/current.json")
33
+ end.should raise_error(UserVoice::Unauthorized)
34
+ end
35
+
36
+ it "should be able to get access token as owner" do
37
+ subject.login_as_owner do |owner|
38
+ owner.get("/api/v1/users/current.json")['user']['roles']['owner'].should == true
39
+
40
+ owner.login_as('regular@example.com') do |regular|
41
+ owner.get("/api/v1/users/current.json")['user']['roles']['owner'].should == true
42
+ @user = regular.get("/api/v1/users/current.json")['user']
43
+ @user['roles']['owner'].should == false
44
+ end
45
+
46
+ owner.get("/api/v1/users/current.json")['user']['roles']['owner'].should == true
47
+ end
48
+ # ensure blocks got run
49
+ @user['email'].should == 'regular@example.com'
50
+ end
51
+
52
+ it "should not be able to create KB article as nobody" do
53
+ lambda do
54
+ result = subject.post("/api/v1/articles.json", :article => {
55
+ :title => 'good morning'
56
+ })
57
+ end.should raise_error(UserVoice::Unauthorized)
58
+ end
59
+
60
+ it "should be able to create and delete a forum as the owner" do
61
+ owner = subject.login_as_owner
62
+ forum = owner.post("/api/v1/forums.json", :forum => {
63
+ :name => 'Test forum from RSpec',
64
+ 'private' => true,
65
+ 'allow_by_email_domain' => true,
66
+ 'allowed_email_domains' => [{'domain' => 'raimo.rspec.example.com'}]
67
+ })['forum']
68
+
69
+ forum['id'].should be_a(Integer)
70
+
71
+ deleted_forum = owner.delete("/api/v1/forums/#{forum['id']}.json")['forum']
72
+ deleted_forum['id'].should == forum['id']
73
+ end
74
+
75
+ it "should get current user with 2-legged call" do
76
+ user = subject.login_as('mailaddress@example.com') do |token|
77
+ token.get("/api/v1/users/current.json")['user']
78
+ end
79
+
80
+ user['email'].should == 'mailaddress@example.com'
81
+ end
82
+
83
+ it "should get current user with copied access token" do
84
+ original_token = subject.login_as('mailaddress@example.com')
85
+
86
+ client = UserVoice::Client.new(config['subdomain_name'],
87
+ config['api_key'],
88
+ config['api_secret'],
89
+ :uservoice_domain => config['uservoice_domain'],
90
+ :protocol => config['protocol'],
91
+ :oauth_token => original_token.token,
92
+ :oauth_token_secret => original_token.secret)
93
+ # Also this works but creates an extra object:
94
+ # client = client.login_with_access_token(original_token.token, original_token.secret)
95
+
96
+ user = client.get("/api/v1/users/current.json")['user']
97
+
98
+ user['email'].should == 'mailaddress@example.com'
99
+ end
100
+
101
+ it "should login as an owner" do
102
+ me = subject.login_as_owner
103
+
104
+ owner = me.get("/api/v1/users/current.json")['user']
105
+ owner['roles']['owner'].should == true
106
+ end
107
+
108
+ it "should not be able to delete when not deleting on behalf of anyone" do
109
+ lambda {
110
+ result = subject.delete("/api/v1/users/#{234}.json")
111
+ }.should raise_error(UserVoice::Unauthorized, /user required/i)
112
+ end
113
+
114
+ it "should not be able to delete owner" do
115
+ owner_access_token = subject.login_as_owner
116
+
117
+ owner = owner_access_token.get("/api/v1/users/current.json")['user']
118
+
119
+ lambda {
120
+ result = owner_access_token.delete("/api/v1/users/#{owner['id']}.json")
121
+ }.should raise_error(UserVoice::Unauthorized, /last owner/i)
122
+ end
123
+
124
+ it "should not be able to delete user without login" do
125
+ regular_user = subject.login_as('somebodythere@example.com').get("/api/v1/users/current.json")['user']
126
+
127
+ lambda {
128
+ subject.delete("/api/v1/users/#{regular_user['id']}.json")
129
+ }.should raise_error(UserVoice::Unauthorized)
130
+ end
131
+
132
+ it "should be able to identify suggestions" do
133
+ owner_token = subject.login_as_owner
134
+ external_scope='sync_to_moon'
135
+ suggestions = owner_token.get("/api/v1/suggestions.json?filter=with_external_id&external_scope=#{external_scope}&manual_action=#{external_scope}")['suggestions']
136
+
137
+ identifications = suggestions.map {|s| { :id => s['id'], :external_id => s['id'].to_i*10 } }
138
+
139
+ ids = owner_token.put("/api/v1/suggestions/identify.json",
140
+ :external_scope => external_scope,
141
+ :identifications => identifications)['identifications']['ids']
142
+ ids.should == identifications.map { |s| s[:id] }.sort
143
+ end
144
+
145
+ it "should be able to delete itself" do
146
+ my_token = subject.login_as('somebodythere@example.com')
147
+
148
+ # whoami
149
+ my_id = my_token.get("/api/v1/users/current.json")['user']['id']
150
+
151
+ # Delete myself!
152
+ my_token.delete("/api/v1/users/#{my_id}.json")['user']['id'].should == my_id
153
+
154
+ # I don't exist anymore
155
+ lambda {
156
+ my_token.get("/api/v1/users/current.json")
157
+ }.should raise_error(UserVoice::NotFound)
158
+ end
159
+
160
+ it "should/be able to delete random user and login as him after that" do
161
+ somebody = subject.login_as('somebodythere@example.com')
162
+ owner = subject.login_as_owner
163
+
164
+ # somebody is still there...
165
+ regular_user = somebody.get("/api/v1/users/current.json")['user']
166
+ regular_user['email'].should == 'somebodythere@example.com'
167
+
168
+ # delete somebody!
169
+ owner.delete("/api/v1/users/#{regular_user['id']}.json")['user']['id'].should == regular_user['id']
170
+
171
+ # not found anymore!
172
+ lambda {
173
+ somebody.get("/api/v1/users/current.json")['errors']['type']
174
+ }.should raise_error(UserVoice::NotFound)
175
+
176
+ # this recreates somebody
177
+ somebody = subject.login_as('somebodythere@example.com')
178
+ somebody.get("/api/v1/users/current.json")['user']['id'].should_not == regular_user['id']
179
+ end
180
+
181
+ it "should raise error with invalid email parameter" do
182
+ expect { subject.login_as('ma') }.to raise_error(UserVoice::Unauthorized)
183
+ expect { subject.login_as(nil) }.to raise_error(UserVoice::Unauthorized)
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,10 @@
1
+ require 'uservoice'
2
+ require 'yaml'
3
+
4
+ def config
5
+ begin
6
+ YAML.load_file(File.expand_path('../config.yml', __FILE__))
7
+ rescue Errno::ENOENT
8
+ raise "Configure your own config.yml and place it in the spec directory"
9
+ end
10
+ end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "uservoice/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "uservoice-ruby"
7
+ s.version = Uservoice::VERSION
8
+ s.authors = ["Raimo Tuisku"]
9
+ s.email = ["dev@usevoice.com"]
10
+ s.homepage = "http://developer.uservoice.com"
11
+ s.summary = %q{Client library for UserVoice API}
12
+ s.description = %q{The gem provides Ruby-bindings to UserVoice API and helps generating Single-Sign-On tokens.}
13
+
14
+ s.rubyforge_project = "uservoice-ruby"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_development_dependency "rspec"
22
+ s.add_runtime_dependency 'ezcrypto'
23
+ s.add_runtime_dependency 'json'
24
+ s.add_runtime_dependency 'oauth'
25
+ end
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: uservoice-ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Raimo Tuisku
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-21 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &70340927735480 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70340927735480
25
+ - !ruby/object:Gem::Dependency
26
+ name: ezcrypto
27
+ requirement: &70340927734840 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70340927734840
36
+ - !ruby/object:Gem::Dependency
37
+ name: json
38
+ requirement: &70340927732680 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70340927732680
47
+ - !ruby/object:Gem::Dependency
48
+ name: oauth
49
+ requirement: &70340927731780 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *70340927731780
58
+ description: The gem provides Ruby-bindings to UserVoice API and helps generating
59
+ Single-Sign-On tokens.
60
+ email:
61
+ - dev@usevoice.com
62
+ executables: []
63
+ extensions: []
64
+ extra_rdoc_files: []
65
+ files:
66
+ - .gitignore
67
+ - Gemfile
68
+ - README.md
69
+ - Rakefile
70
+ - lib/uservoice-ruby.rb
71
+ - lib/uservoice/user_voice.rb
72
+ - lib/uservoice/version.rb
73
+ - spec/config.yml.templ
74
+ - spec/lib/user_voice_spec.rb
75
+ - spec/spec_helper.rb
76
+ - uservoice-ruby.gemspec
77
+ homepage: http://developer.uservoice.com
78
+ licenses: []
79
+ post_install_message:
80
+ rdoc_options: []
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ! '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ! '>='
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubyforge_project: uservoice-ruby
97
+ rubygems_version: 1.8.15
98
+ signing_key:
99
+ specification_version: 3
100
+ summary: Client library for UserVoice API
101
+ test_files:
102
+ - spec/config.yml.templ
103
+ - spec/lib/user_voice_spec.rb
104
+ - spec/spec_helper.rb