f1api 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +5 -0
- data/Gemfile.lock +27 -0
- data/LICENSE +19 -0
- data/README.md +24 -0
- data/Rakefile +38 -0
- data/VERSION +1 -0
- data/VERSION.yml +5 -0
- data/config/f1-oauth.yml +48 -0
- data/lib/f1api.rb +14 -0
- data/lib/f1api/activeresource/base.rb +19 -0
- data/lib/f1api/activeresource/connection.rb +39 -0
- data/lib/f1api/client.rb +62 -0
- data/lib/f1api/configuration.rb +63 -0
- data/lib/f1api/oauth.rb +54 -0
- data/lib/f1api/oauth/credentials_authentication.rb +59 -0
- data/lib/f1api/oauth/oauth_authentication.rb +72 -0
- data/test/fixtures/access_token.rb +10 -0
- data/test/fixtures/http.rb +13 -0
- data/test/fixtures/request_token.rb +10 -0
- data/test/unit/configuration_test.rb +60 -0
- data/test/unit/credentials_test.rb +59 -0
- data/test/unit/oauth_test.rb +74 -0
- metadata +185 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
activemodel (3.0.3)
|
5
|
+
activesupport (= 3.0.3)
|
6
|
+
builder (~> 2.1.2)
|
7
|
+
i18n (~> 0.4)
|
8
|
+
activeresource (3.0.3)
|
9
|
+
activemodel (= 3.0.3)
|
10
|
+
activesupport (= 3.0.3)
|
11
|
+
activesupport (3.0.3)
|
12
|
+
builder (2.1.2)
|
13
|
+
i18n (0.4.2)
|
14
|
+
mocha (0.9.9)
|
15
|
+
rake
|
16
|
+
nokogiri (1.4.4)
|
17
|
+
oauth (0.4.4)
|
18
|
+
rake (0.8.7)
|
19
|
+
|
20
|
+
PLATFORMS
|
21
|
+
ruby
|
22
|
+
|
23
|
+
DEPENDENCIES
|
24
|
+
activeresource
|
25
|
+
mocha
|
26
|
+
nokogiri
|
27
|
+
oauth (= 0.4.4)
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2010 Fellowship Technologies
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Fellowship One REST API Ruby Client Library
|
2
|
+
===========================================
|
3
|
+
|
4
|
+
**NOTE**: This library is of alpha quality. It is not meant for use in production apps. It's definitely not feature complete and it may have bugs.
|
5
|
+
|
6
|
+
Introduction
|
7
|
+
------------
|
8
|
+
This library is an implementation of the Fellowship One REST API. Currently we are only fielding requests and handling the OAuth tokens. The goal is to have a library that works like ActiveResource.
|
9
|
+
|
10
|
+
Usage
|
11
|
+
-----
|
12
|
+
require 'f1api'
|
13
|
+
|
14
|
+
class Person < FellowshipOneAPI::Base
|
15
|
+
end
|
16
|
+
|
17
|
+
client.authorize!
|
18
|
+
# If using creds in YAML file:
|
19
|
+
# client.authorize! "username", "password"
|
20
|
+
|
21
|
+
Person.connect client
|
22
|
+
|
23
|
+
Person.find(12345)
|
24
|
+
Person.find("search", {:searchFor => "Dearing", :include => "communications,addresses"})
|
data/Rakefile
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
BASE_DIR = File.dirname(__FILE__)
|
2
|
+
|
3
|
+
task :test do
|
4
|
+
require 'bundler/setup'
|
5
|
+
require 'test/unit'
|
6
|
+
require 'mocha'
|
7
|
+
require "#{BASE_DIR}/test/fixtures/request_token.rb"
|
8
|
+
require "#{BASE_DIR}/test/fixtures/access_token.rb"
|
9
|
+
require "#{BASE_DIR}/test/fixtures/http.rb"
|
10
|
+
require "#{BASE_DIR}/lib/f1api"
|
11
|
+
Dir["#{BASE_DIR}/test/unit/*.rb"].each do |test|
|
12
|
+
require test
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
task :rdoc do
|
17
|
+
`rdoc -x 'test/*'`
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'jeweler'
|
21
|
+
Jeweler::Tasks.new do |gem|
|
22
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
23
|
+
gem.name = "f1api"
|
24
|
+
gem.homepage = "http://github.com/jessedearing/f1api"
|
25
|
+
gem.license = "MIT"
|
26
|
+
gem.summary = %Q{Consume the Fellowship One API in your apps using ActiveResource}
|
27
|
+
gem.description = %Q{Consumes the Fellowship One API in your apps using ActiveResource. Implements 2nd party credentials-based authenticaion and full OAuth implementation. }
|
28
|
+
gem.email = "jdearing@fellowshiptech.com"
|
29
|
+
gem.authors = ["Jesse Dearing"]
|
30
|
+
# Include your dependencies below. Runtime dependencies are required when using your gem,
|
31
|
+
# and development dependencies are only needed for development (ie running rake tasks, tests, etc)
|
32
|
+
# gem.add_runtime_dependency 'jabber4r', '> 0.1'
|
33
|
+
# gem.add_development_dependency 'rspec', '> 1.2.3'
|
34
|
+
gem.add_runtime_dependency 'oauth', '= 0.4.4'
|
35
|
+
gem.add_development_dependency 'mocha'
|
36
|
+
gem.add_runtime_dependency 'activeresource'
|
37
|
+
end
|
38
|
+
Jeweler::RubygemsDotOrgTasks.new
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.9.0
|
data/VERSION.yml
ADDED
data/config/f1-oauth.yml
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
development:
|
2
|
+
consumer_key: ""
|
3
|
+
consumer_secret: ""
|
4
|
+
site_url: "https://{church_code}.staging.fellowshiponeapi.com"
|
5
|
+
# Store the church code if you are writing a 2nd Party implementation
|
6
|
+
# Otherwise, you'll want to ask for the church code from the user
|
7
|
+
church_code: ""
|
8
|
+
authentication_type: "oauth" #oauth or credentials
|
9
|
+
request_token_path: "/v1/Tokens/RequestToken"
|
10
|
+
access_token_path: "/v1/Tokens/AccessToken" #OAuth only
|
11
|
+
# OAuth authentication vars
|
12
|
+
portal_authorize_path: "/v1/PortalUser/Login"
|
13
|
+
weblink_authorize_path: "/v1/WeblinkUser/Login"
|
14
|
+
# Use the following for credentials based authentication
|
15
|
+
portal_credential_token_path: "/v1/PortalUser/AccessToken"
|
16
|
+
weblink_credential_token_path: "/v1/WeblinkUser/AccessToken"
|
17
|
+
test:
|
18
|
+
consumer_key: "123456789"
|
19
|
+
consumer_secret: "12345678-90ab-cdef-0123-4567890abcde"
|
20
|
+
site_url: "https://{church_code}.staging.fellowshiponeapi.com"
|
21
|
+
# Store the church code if you are writing a 2nd Party implementation
|
22
|
+
# Otherwise, you'll want to ask for the church code from the user
|
23
|
+
church_code: "test"
|
24
|
+
authentication_type: "oauth" #oauth or credentials
|
25
|
+
request_token_path: "/V1/Tokens/RequestToken"
|
26
|
+
access_token_path: "/V1/Tokens/AccessToken" #OAuth only
|
27
|
+
# OAuth authentication vars
|
28
|
+
portal_authorize_path: "/V1/PortalUser/Login"
|
29
|
+
weblink_authorize_path: "/V1/WeblinkUser/Login"
|
30
|
+
# Use the following for credentials based authentication
|
31
|
+
portal_credential_token_path: "/V1/PortalUser/AccessToken"
|
32
|
+
weblink_credential_token_path: "/V1/WeblinkUser/AccessToken"
|
33
|
+
production:
|
34
|
+
consumer_key: ""
|
35
|
+
consumer_secret: ""
|
36
|
+
site_url: "https://{church_code}.fellowshiponeapi.com"
|
37
|
+
# Store the church code if you are writing a 2nd Party implementation
|
38
|
+
# Otherwise, you'll want to ask for the church code from the user
|
39
|
+
church_code: ""
|
40
|
+
authentication_type: "oauth" #oauth or credentials
|
41
|
+
request_token_path: "/v1/Tokens/RequestToken"
|
42
|
+
access_token_path: "/v1/Tokens/AccessToken" #OAuth only
|
43
|
+
# OAuth authentication vars
|
44
|
+
portal_authorize_path: "/v1/PortalUser/Login"
|
45
|
+
weblink_authorize_path: "/v1/WeblinkUser/Login"
|
46
|
+
# Use the following for credentials based authentication
|
47
|
+
portal_credential_token_path: "/v1/PortalUser/AccessToken"
|
48
|
+
weblink_credential_token_path: "/v1/WeblinkUser/AccessToken"
|
data/lib/f1api.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}"
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'yaml'
|
4
|
+
require 'oauth'
|
5
|
+
require 'base64'
|
6
|
+
require 'uri'
|
7
|
+
require 'active_resource'
|
8
|
+
require "f1api/configuration"
|
9
|
+
require "f1api/oauth"
|
10
|
+
require "f1api/oauth/credentials_authentication"
|
11
|
+
require "f1api/oauth/oauth_authentication"
|
12
|
+
require "f1api/client"
|
13
|
+
require "f1api/activeresource/connection"
|
14
|
+
require "f1api/activeresource/base"
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module FellowshipOneAPI # :nodoc:
|
2
|
+
# The Base class should be inherited by all model classes as it provides the facilities that the class will need
|
3
|
+
class Base < ActiveResource::Base
|
4
|
+
self.site = "#{Configuration.site_url}/v1"
|
5
|
+
# Creates a new connection
|
6
|
+
#
|
7
|
+
# ==Examples
|
8
|
+
# Person.connect(FellowshipOneAPI::Client.new)
|
9
|
+
#
|
10
|
+
# If the connection needs to be forcibly refreshed then you can pass true
|
11
|
+
# Person.connect(FellowshipOneAPI::Client.new, true)
|
12
|
+
def self.connect(client, refresh = false)
|
13
|
+
if(refresh or @connection.nil?)
|
14
|
+
@connection = Connection.new(client, client.consumer.site, format)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module FellowshipOneAPI
|
4
|
+
# Creating a wrapper for the ActiveResource::Connection class
|
5
|
+
class Connection < ActiveResource::Connection
|
6
|
+
# Pass in a new connection to the API
|
7
|
+
def initialize(f1api_connection, *args)
|
8
|
+
@f1api_connection = f1api_connection
|
9
|
+
super(*args)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
# The request method that is passes the request through to the F1 API client
|
14
|
+
def request(method, path, *args)
|
15
|
+
if @f1api_connection == nil
|
16
|
+
super(method, path, *args)
|
17
|
+
else
|
18
|
+
response = @f1api_connection.request(method, path, *args)
|
19
|
+
|
20
|
+
if method == :get
|
21
|
+
response.body = transform_response response.body, path
|
22
|
+
end
|
23
|
+
handle_response(response)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
#
|
28
|
+
def transform_response(response_body, path)
|
29
|
+
n = Nokogiri::XML(response_body)
|
30
|
+
res = (n/"results")
|
31
|
+
if not (res.empty?)
|
32
|
+
resource = ((path.split '/')[2]).downcase
|
33
|
+
res[0].name = resource
|
34
|
+
end
|
35
|
+
|
36
|
+
n.to_s
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/f1api/client.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
module FellowshipOneAPI
|
2
|
+
# ==The Fellowship One API client class
|
3
|
+
# Takes an HTTP request and passes it through the OAuth library which added the approperiate headers and querystring
|
4
|
+
# parameters.
|
5
|
+
# ===Examples
|
6
|
+
# [Simple client using OAuth and default YAML config values]
|
7
|
+
# client = FellowshipOneAPI::Client.new
|
8
|
+
#
|
9
|
+
# client.authorize!
|
10
|
+
#
|
11
|
+
# client.request(:get, '/v1/People/123.xml')
|
12
|
+
#
|
13
|
+
# [Using credentials based authentication (2nd party)]
|
14
|
+
# client = FellowshipOneAPI::Client.new({:auth_type => :credentials})
|
15
|
+
#
|
16
|
+
# client.authorize!("username", "password")
|
17
|
+
#
|
18
|
+
# client.request(:get, '/v1/People/123.xml')
|
19
|
+
#
|
20
|
+
# [Authenticating against weblink passing credentials]
|
21
|
+
# client = FellowshipOneAPI::Client.new({:auth_type => :credentials, :auth_against => :weblink})
|
22
|
+
#
|
23
|
+
# client.authorize("weblinkuser", "weblinkpassword")
|
24
|
+
#
|
25
|
+
# client.request(:get, '/v1/People/123.xml')
|
26
|
+
#
|
27
|
+
# [Loading a client with an existing access token]
|
28
|
+
# client = FellowshipOneAPI::Client.new({:oauth_token => "123456", :oauth_token_secret => "987654"})
|
29
|
+
#
|
30
|
+
# client.request(:get, '/v1/People/123.xml')
|
31
|
+
class Client
|
32
|
+
# Creates a new instance of a client used to connect with the Fellowship One API
|
33
|
+
# The client can be configured with the following symbols:
|
34
|
+
# [+:auth_type+] - Can be _:credentials_ or _:oauth_ (_:oauth_ is the default)
|
35
|
+
# [+:auth_against+] - Can be _:portal_ or _:weblink_ (_:portal_ is the default)
|
36
|
+
# [+:oauth_token+] - The access token
|
37
|
+
# [+:oauth_token_secret+] - The access token secret
|
38
|
+
def initialize(args = {})
|
39
|
+
args[:auth_type] ||= Configuration.authentication_type.to_sym
|
40
|
+
if args[:auth_type] == :credentials
|
41
|
+
extend OAuth::CredentialsAuthentication
|
42
|
+
else
|
43
|
+
extend OAuth::OAuthAuthentication
|
44
|
+
end
|
45
|
+
|
46
|
+
if(args[:auth_against])
|
47
|
+
load_consumer_config args[:auth_against]
|
48
|
+
else
|
49
|
+
load_consumer_config
|
50
|
+
end
|
51
|
+
|
52
|
+
if(args[:oauth_token] and args[:oauth_token_secret])
|
53
|
+
@oauth_access_token = ::OAuth::AccessToken.from_hash(@oauth_consumer, args[:oauth_token], args[:oauth_token_secret])
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Passes through the request to the OAuth library to be signed and set out HTTP
|
58
|
+
def request(*args)
|
59
|
+
@oauth_access_token.request(*args)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module FellowshipOneAPI # :nodoc:
|
2
|
+
# This accesses the YAML-based F1 API config file
|
3
|
+
#
|
4
|
+
# This class was written to take rails environment variables like +RAILS_ENV+ and +Rails.root+ into account
|
5
|
+
class Configuration
|
6
|
+
# Explictly defines where the configuration file is
|
7
|
+
def self.file_path=(path)
|
8
|
+
@file_path
|
9
|
+
end
|
10
|
+
|
11
|
+
# Gets the specified key from the configuration file
|
12
|
+
# [Example] FellowshipTechAPIClient.Configuration["consumer_key"] <i># "2"</i>
|
13
|
+
#
|
14
|
+
# FellowshipTechAPIClient.Configuration["consumer_secret"] <i># "12345678-9abc-def0-1234-567890abcdef"</i>
|
15
|
+
def self.[](value)
|
16
|
+
load_yaml if @config_yaml.nil?
|
17
|
+
val = @config_yaml[self.environment][value]
|
18
|
+
# if we have the string has "{church_code}" then we'll substitute it
|
19
|
+
if val =~ /\{church_code\}/ and value != "church_code"
|
20
|
+
return val.gsub("{church_code}", self["church_code"])
|
21
|
+
end
|
22
|
+
return val
|
23
|
+
end
|
24
|
+
|
25
|
+
# Gets the current environment
|
26
|
+
def self.environment
|
27
|
+
@environment ||= "development"
|
28
|
+
@environment ||= ::Rails.env if defined? ::Rails
|
29
|
+
@environment
|
30
|
+
end
|
31
|
+
|
32
|
+
# Set the current environment
|
33
|
+
def self.environment=(env_value)
|
34
|
+
@environment = env_value
|
35
|
+
end
|
36
|
+
|
37
|
+
# Overridden method_missing to facilitate a more pleasing ruby-like syntax for accessing
|
38
|
+
# configuration values
|
39
|
+
def self.method_missing(name, *args, &block)
|
40
|
+
return self[name.to_s] unless self[name.to_s].nil?
|
41
|
+
super
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# Loads the YAML file
|
47
|
+
#
|
48
|
+
# Starts by looking to see if file_path is defined then checks in current directory (.) and then your Rails.root and then the config
|
49
|
+
# directory off of the base directory of the gem
|
50
|
+
def self.load_yaml
|
51
|
+
if not @file_path.nil?
|
52
|
+
@config_yaml = YAML.load_file(@file_path)
|
53
|
+
elsif File.exists? "./f1-oauth.yml"
|
54
|
+
@config_yaml = YAML.load_file("./f1-oauth.yml")
|
55
|
+
elsif defined? ::Rails
|
56
|
+
@config_yaml = YAML.load_file("#{::Rails.root}/config/f1-oauth.yml")
|
57
|
+
else
|
58
|
+
path = File.dirname(__FILE__) + "/../../config/f1-oauth.yml"
|
59
|
+
@config_yaml = YAML.load_file(path)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/f1api/oauth.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
module FellowshipOneAPI # :nodoc:
|
2
|
+
# Wrapper around the OAuth v1.0 specification using the +oauth+ gem.
|
3
|
+
#
|
4
|
+
# The Fellowship One API has two methods of authentication:
|
5
|
+
# [OAuthAuthentication] This is the default method if no method is declared. This method allows Fellowship Tech to handle
|
6
|
+
# the authentication and we redirect back to your app.
|
7
|
+
# [CredentialsAuthentication] The methods lets the consumer submit credentials and verify authentication.
|
8
|
+
module OAuth
|
9
|
+
# The OAuth consumer key.
|
10
|
+
# This will get set automatically from the YAML config file if not set explictly
|
11
|
+
attr_accessor :oauth_consumer_key
|
12
|
+
alias :consumer_key :oauth_consumer_key
|
13
|
+
alias :consumer_key= :oauth_consumer_key=
|
14
|
+
|
15
|
+
# The OAuth consumer secret.
|
16
|
+
# This will get set automatically from the YAML config file if not set explictly
|
17
|
+
attr_accessor :oauth_consumer_secret
|
18
|
+
alias :consumer_secret :oauth_consumer_secret
|
19
|
+
alias :consumer_secret= :oauth_consumer_secret=
|
20
|
+
|
21
|
+
# The OAuth access token object where all requests are made off of
|
22
|
+
attr_reader :oauth_access_token
|
23
|
+
alias :access_token :oauth_access_token
|
24
|
+
|
25
|
+
# The OAuth consumer object
|
26
|
+
attr_reader :oauth_consumer
|
27
|
+
alias :consumer :oauth_consumer
|
28
|
+
|
29
|
+
# The URI for the resource of the authenticated user
|
30
|
+
attr_reader :authenticated_user_uri
|
31
|
+
|
32
|
+
# Creates the OAuth consumer object
|
33
|
+
def load_consumer_config(type = :portal)
|
34
|
+
case type
|
35
|
+
when :portal
|
36
|
+
authorize_path = FellowshipOneAPI::Configuration.portal_authorize_path
|
37
|
+
when :weblink
|
38
|
+
authorize_path = FellowshipOneAPI::Configuration.weblink_authorize_path
|
39
|
+
end
|
40
|
+
|
41
|
+
@oauth_consumer_key ||= FellowshipOneAPI::Configuration.consumer_key
|
42
|
+
@oauth_consumer_secret ||= FellowshipOneAPI::Configuration.consumer_secret
|
43
|
+
|
44
|
+
@oauth_consumer = ::OAuth::Consumer.new(@oauth_consumer_key,
|
45
|
+
@oauth_consumer_secret,
|
46
|
+
{:site => FellowshipOneAPI::Configuration.site_url,
|
47
|
+
:request_token_path => FellowshipOneAPI::Configuration.request_token_path,
|
48
|
+
:access_token_path => FellowshipOneAPI::Configuration.access_token_path,
|
49
|
+
:authorize_path => authorize_path })
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module FellowshipOneAPI # :nodoc:
|
2
|
+
module OAuth
|
3
|
+
# Implements the Credentials method of authentication. You must manage the credentials.
|
4
|
+
module CredentialsAuthentication
|
5
|
+
include OAuth
|
6
|
+
# Authorizes a user
|
7
|
+
# +username+:: The username of the user
|
8
|
+
# +password+:: The password of the user
|
9
|
+
# +type+:: Can be :portal or :weblink based on which credentials you want to authenticate against
|
10
|
+
# Returns the URI for the authenticated user
|
11
|
+
def authorize!(username, password, type = :portal)
|
12
|
+
load_consumer_config(type) if @oauth_consumer.nil?
|
13
|
+
|
14
|
+
@oauth_request = @oauth_consumer.get_request_token
|
15
|
+
cred = URI.encode(Base64.encode64("#{username} #{password}"))
|
16
|
+
|
17
|
+
case type
|
18
|
+
when :portal
|
19
|
+
auth_url = FellowshipOneAPI::Configuration.portal_credential_token_path
|
20
|
+
when :weblink
|
21
|
+
auth_url = FellowshipOneAPI::Configuration.weblink_credential_token_path
|
22
|
+
end
|
23
|
+
|
24
|
+
response = @oauth_consumer.request(:post, auth_url, nil, {}, "ec=#{cred}", {'Content-Type' => 'application/x-www-form-urlencoded'})
|
25
|
+
|
26
|
+
handle_response_code(response)
|
27
|
+
|
28
|
+
# Gettting the URI of the authenticated user
|
29
|
+
@authenticated_user_uri = response["Content-Location"]
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def handle_response_code(response)
|
34
|
+
case response.code.to_i
|
35
|
+
when (200..299)
|
36
|
+
@oauth_access_token = ::OAuth::AccessToken.from_hash(@oauth_consumer, parse_access_token(response.body))
|
37
|
+
when (300..399)
|
38
|
+
# redirect
|
39
|
+
# TODO: actually redirect instead of throwing error
|
40
|
+
response.error!
|
41
|
+
when (400..499)
|
42
|
+
raise OAuth::Unauthorized, response
|
43
|
+
else
|
44
|
+
response.error!
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Parse returned OAuth access token key/secret pair
|
49
|
+
def parse_access_token(response)
|
50
|
+
oauth_hash = {}
|
51
|
+
response.split('&').each do |val|
|
52
|
+
kv = val.split('=')
|
53
|
+
oauth_hash.merge!({kv[0].to_sym => kv[1]})
|
54
|
+
end
|
55
|
+
oauth_hash
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module FellowshipOneAPI # :nodoc:
|
2
|
+
module OAuth
|
3
|
+
# Implements the pure OAuth method of authentication. This allows the Fellowship One API to
|
4
|
+
# manage the authentication process.
|
5
|
+
module OAuthAuthentication
|
6
|
+
include OAuth
|
7
|
+
# The OAuth request object
|
8
|
+
attr_reader :oauth_request
|
9
|
+
|
10
|
+
# The OAuth authorization URI
|
11
|
+
attr_reader :oauth_authorize_url
|
12
|
+
alias :authorize_url :oauth_authorize_url
|
13
|
+
|
14
|
+
# Gets a new request token and return the authorize URI
|
15
|
+
# +type+:: Can be :portal or :weblink based on which credentials you want to authenticate against
|
16
|
+
def authorize!(type = :portal)
|
17
|
+
load_consumer_config(type) if oauth_consumer.nil?
|
18
|
+
|
19
|
+
@oauth_request = oauth_consumer.get_request_token
|
20
|
+
@oauth_authorize_url = oauth_request.authorize_url
|
21
|
+
|
22
|
+
@oauth_consumer.instance_eval do
|
23
|
+
# The token request reponse is scoped only in the token_request method, but I need to get access to the response
|
24
|
+
# headers so that I can pull back the Content-Location header and get the authenticated user URI
|
25
|
+
def token_request(http_method, path, token = nil, request_options = {}, *arguments)
|
26
|
+
@tr_response = request(http_method, path, token, request_options, *arguments)
|
27
|
+
case @tr_response.code.to_i
|
28
|
+
|
29
|
+
when (200..299)
|
30
|
+
if block_given?
|
31
|
+
yield @tr_response.body
|
32
|
+
else
|
33
|
+
# symbolize keys
|
34
|
+
# TODO this could be considered unexpected behavior; symbols or not?
|
35
|
+
# TODO this also drops subsequent values from multi-valued keys
|
36
|
+
CGI.parse(@tr_response.body).inject({}) do |h,(k,v)|
|
37
|
+
h[k.strip.to_sym] = v.first
|
38
|
+
h[k.strip] = v.first
|
39
|
+
h
|
40
|
+
end
|
41
|
+
end
|
42
|
+
when (300..399)
|
43
|
+
# this is a redirect
|
44
|
+
@tr_response.error!
|
45
|
+
when (400..499)
|
46
|
+
raise OAuth::Unauthorized, @tr_response
|
47
|
+
else
|
48
|
+
@tr_response.error!
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# The HTTP response from token_request
|
53
|
+
def token_request_response
|
54
|
+
@tr_response
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
oauth_authorize_url
|
59
|
+
end
|
60
|
+
|
61
|
+
# After a the user has been authenticated then we use the access token to access protected resources in the API.
|
62
|
+
# Since the authentication has taken place, we now know about the user that authenticated and
|
63
|
+
# have a URI to the record of that user.
|
64
|
+
#
|
65
|
+
# The URI for the authenticated user is returned.
|
66
|
+
def get_access_token
|
67
|
+
@oauth_access_token = oauth_request.get_access_token
|
68
|
+
@authenticated_user_uri = oauth_consumer.token_request_response["Content-Location"]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
class OAuthTest < Test::Unit::TestCase
|
2
|
+
include FellowshipOneAPI
|
3
|
+
|
4
|
+
def setup
|
5
|
+
Configuration.environment = "test"
|
6
|
+
Configuration.file_path = "#{File.dirname(__FILE__)}/../../config/f1-oauth.yml"
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_method_missing
|
10
|
+
assert_raise NoMethodError do
|
11
|
+
Configuration.asdf
|
12
|
+
end
|
13
|
+
|
14
|
+
assert_nothing_raised do
|
15
|
+
Configuration.consumer_key
|
16
|
+
end
|
17
|
+
|
18
|
+
assert_equal Configuration["consumer_key"], Configuration.consumer_key
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_consumer_key
|
22
|
+
assert_equal("123456789", Configuration.consumer_key)
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_consumer_secret
|
26
|
+
assert_equal("12345678-90ab-cdef-0123-4567890abcde", Configuration.consumer_secret)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_site_url
|
30
|
+
assert_equal("https://test.staging.fellowshiponeapi.com", Configuration.site_url)
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_church_code
|
34
|
+
assert_equal("test", Configuration.church_code)
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_request_token_path
|
38
|
+
assert_equal("/V1/Tokens/RequestToken", Configuration.request_token_path)
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_access_token_path
|
42
|
+
assert_equal("/V1/Tokens/AccessToken", Configuration.access_token_path)
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_portal_authorize_path
|
46
|
+
assert_equal("/V1/PortalUser/Login", Configuration.portal_authorize_path)
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_weblink_authorize_path
|
50
|
+
assert_equal("/V1/WeblinkUser/Login", Configuration.weblink_authorize_path)
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_portal_credential_token_path
|
54
|
+
assert_equal("/V1/PortalUser/AccessToken", Configuration.portal_credential_token_path)
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_weblink_credential_token_path
|
58
|
+
assert_equal("/V1/WeblinkUser/AccessToken", Configuration.weblink_credential_token_path)
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
class CredentialsAuthenticationTest
|
2
|
+
include FellowshipOneAPI::OAuth::CredentialsAuthentication
|
3
|
+
attr_accessor :oauth_consumer
|
4
|
+
end
|
5
|
+
|
6
|
+
class CredentialsTest < Test::Unit::TestCase
|
7
|
+
include FellowshipOneAPI
|
8
|
+
def setup
|
9
|
+
Configuration.environment = "test"
|
10
|
+
Configuration.file_path = "#{File.dirname(__FILE__)}/../../config/f1-oauth.yml"
|
11
|
+
|
12
|
+
@test_username = "testuser"
|
13
|
+
@test_password = "testpass"
|
14
|
+
|
15
|
+
@cred_test = CredentialsAuthenticationTest.new
|
16
|
+
@cred_test.load_consumer_config
|
17
|
+
@mocked_access_token = AccessTokenFixture.get(@cred_test.oauth_consumer)
|
18
|
+
@mocked_request_token = RequestTokenFixture.get(@cred_test.oauth_consumer)
|
19
|
+
cred = URI.encode(Base64.encode64("#{@test_username} #{@test_password}"))
|
20
|
+
|
21
|
+
@mocked_user_uri = "#{Configuration.site_url}/V1/People/123456"
|
22
|
+
|
23
|
+
@cred_test.oauth_consumer.stubs(:get_request_token).returns(@mocked_request_token)
|
24
|
+
|
25
|
+
@cred_test.oauth_consumer.stubs(:request).with(:post, ::FellowshipOneAPI::Configuration.portal_credential_token_path,
|
26
|
+
nil, {}, "ec=#{cred}", {'Content-Type' => 'application/x-www-form-urlencoded'}).
|
27
|
+
returns(HttpFixture.new)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_portal_authorize!
|
31
|
+
actual = @cred_test.authorize! @test_username, @test_password
|
32
|
+
|
33
|
+
assert_equal(@mocked_user_uri, actual)
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_weblink_authorize!
|
37
|
+
cred = URI.encode(Base64.encode64("#{@test_username} #{@test_password}"))
|
38
|
+
@cred_test.oauth_consumer = nil
|
39
|
+
@cred_test.load_consumer_config(:weblink)
|
40
|
+
@cred_test.oauth_consumer.expects(:get_request_token).returns(@mocked_request_token).at_least_once
|
41
|
+
@cred_test.oauth_consumer.expects(:request).with(:post, ::FellowshipOneAPI::Configuration.weblink_credential_token_path,
|
42
|
+
nil, {}, "ec=#{cred}", {'Content-Type' => 'application/x-www-form-urlencoded'}).returns(
|
43
|
+
HttpFixture.new).at_least_once
|
44
|
+
@cred_test.authorize! @test_username, @test_password, :weblink
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_get_access_token
|
48
|
+
@cred_test.authorize! @test_username, @test_password
|
49
|
+
|
50
|
+
assert_equal("access", @cred_test.access_token.token)
|
51
|
+
assert_equal("token", @cred_test.access_token.secret)
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_authorized_user_uri
|
55
|
+
@cred_test.authorize! @test_username, @test_password
|
56
|
+
|
57
|
+
assert_equal(@mocked_user_uri, @cred_test.authenticated_user_uri)
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
class OAuthAuthenticationTest
|
2
|
+
include FellowshipOneAPI::OAuth::OAuthAuthentication
|
3
|
+
attr_accessor :oauth_consumer
|
4
|
+
end
|
5
|
+
|
6
|
+
class OAuthTest < Test::Unit::TestCase
|
7
|
+
include FellowshipOneAPI
|
8
|
+
def setup
|
9
|
+
Configuration.environment = "test"
|
10
|
+
Configuration.file_path = "#{File.dirname(__FILE__)}/../../config/f1-oauth.yml"
|
11
|
+
|
12
|
+
@oauth_test = OAuthAuthenticationTest.new
|
13
|
+
@oauth_test.load_consumer_config
|
14
|
+
@mocked_request_token = RequestTokenFixture.get(@oauth_test.oauth_consumer)
|
15
|
+
@mocked_access_token = AccessTokenFixture.get(@oauth_test.oauth_consumer)
|
16
|
+
@oauth_test.oauth_consumer.stubs(:get_request_token).returns(@mocked_request_token)
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_portal_authorize!
|
20
|
+
@oauth_test.oauth_consumer.expects(:get_request_token).returns(@mocked_request_token)
|
21
|
+
|
22
|
+
assert_equal("#{Configuration.site_url}#{Configuration.portal_authorize_path}?oauth_token=request", @oauth_test.authorize!)
|
23
|
+
|
24
|
+
assert_equal("request", @oauth_test.oauth_request.token)
|
25
|
+
assert_equal("token", @oauth_test.oauth_request.secret)
|
26
|
+
assert_equal("#{Configuration.site_url}#{Configuration.portal_authorize_path}?oauth_token=#{@oauth_test.oauth_request.token}", @oauth_test.authorize_url)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_weblink_authorize!
|
30
|
+
@oauth_test.oauth_consumer = nil
|
31
|
+
@oauth_test.load_consumer_config(:weblink)
|
32
|
+
@mocked_request_token = RequestTokenFixture.get(@oauth_test.oauth_consumer)
|
33
|
+
@oauth_test.oauth_consumer.expects(:get_request_token).returns(@mocked_request_token)
|
34
|
+
|
35
|
+
@oauth_test.authorize!(:weblink)
|
36
|
+
|
37
|
+
assert_equal("request", @oauth_test.oauth_request.token)
|
38
|
+
assert_equal("token", @oauth_test.oauth_request.secret)
|
39
|
+
assert_equal("#{Configuration.site_url}#{Configuration.weblink_authorize_path}?oauth_token=#{@oauth_test.oauth_request.token}", @oauth_test.authorize_url)
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_get_access_token
|
43
|
+
@oauth_test.authorize!
|
44
|
+
|
45
|
+
@oauth_test.oauth_request.expects(:get_access_token).returns(@mocked_access_token)
|
46
|
+
@oauth_test.oauth_consumer.expects(:token_request_response).returns({"Content-Location" => ""})
|
47
|
+
@oauth_test.get_access_token
|
48
|
+
|
49
|
+
assert_equal("access", @oauth_test.access_token.token)
|
50
|
+
assert_equal("token", @oauth_test.access_token.secret)
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_authorized_user_uri
|
54
|
+
mocked_user_uri = "#{Configuration.site_url}/V1/People/123456"
|
55
|
+
|
56
|
+
@oauth_test.authorize!
|
57
|
+
|
58
|
+
@oauth_test.oauth_request.expects(:get_access_token).returns(@mocked_access_token)
|
59
|
+
@oauth_test.oauth_consumer.expects(:token_request_response).returns({"Content-Location" => mocked_user_uri})
|
60
|
+
@oauth_test.get_access_token
|
61
|
+
|
62
|
+
assert_equal(mocked_user_uri, @oauth_test.authenticated_user_uri)
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_change_auth_types
|
66
|
+
@oauth_test.authorize!
|
67
|
+
assert_equal("#{Configuration.site_url}#{Configuration.portal_authorize_path}", @oauth_test.oauth_consumer.authorize_url)
|
68
|
+
@oauth_test.load_consumer_config(:weblink)
|
69
|
+
@oauth_test.oauth_consumer.stubs(:get_request_token).returns(@mocked_request_token)
|
70
|
+
@oauth_test.authorize!
|
71
|
+
assert_equal("#{Configuration.site_url}#{Configuration.weblink_authorize_path}", @oauth_test.oauth_consumer.authorize_url)
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
metadata
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: f1api
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 9
|
8
|
+
- 0
|
9
|
+
version: 0.9.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Jesse Dearing
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-11-19 00:00:00 -06:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: nokogiri
|
22
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
23
|
+
none: false
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 0
|
29
|
+
version: "0"
|
30
|
+
type: :runtime
|
31
|
+
prerelease: false
|
32
|
+
version_requirements: *id001
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: oauth
|
35
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
36
|
+
none: false
|
37
|
+
requirements:
|
38
|
+
- - "="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
segments:
|
41
|
+
- 0
|
42
|
+
- 4
|
43
|
+
- 4
|
44
|
+
version: 0.4.4
|
45
|
+
type: :runtime
|
46
|
+
prerelease: false
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: mocha
|
50
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
segments:
|
56
|
+
- 0
|
57
|
+
version: "0"
|
58
|
+
type: :runtime
|
59
|
+
prerelease: false
|
60
|
+
version_requirements: *id003
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: activeresource
|
63
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
segments:
|
69
|
+
- 0
|
70
|
+
version: "0"
|
71
|
+
type: :runtime
|
72
|
+
prerelease: false
|
73
|
+
version_requirements: *id004
|
74
|
+
- !ruby/object:Gem::Dependency
|
75
|
+
name: oauth
|
76
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
78
|
+
requirements:
|
79
|
+
- - "="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
segments:
|
82
|
+
- 0
|
83
|
+
- 4
|
84
|
+
- 4
|
85
|
+
version: 0.4.4
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: *id005
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: mocha
|
91
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
segments:
|
97
|
+
- 0
|
98
|
+
version: "0"
|
99
|
+
type: :development
|
100
|
+
prerelease: false
|
101
|
+
version_requirements: *id006
|
102
|
+
- !ruby/object:Gem::Dependency
|
103
|
+
name: activeresource
|
104
|
+
requirement: &id007 !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
segments:
|
110
|
+
- 0
|
111
|
+
version: "0"
|
112
|
+
type: :runtime
|
113
|
+
prerelease: false
|
114
|
+
version_requirements: *id007
|
115
|
+
description: "Consumes the Fellowship One API in your apps using ActiveResource. Implements 2nd party credentials-based authenticaion and full OAuth implementation. "
|
116
|
+
email: jdearing@fellowshiptech.com
|
117
|
+
executables: []
|
118
|
+
|
119
|
+
extensions: []
|
120
|
+
|
121
|
+
extra_rdoc_files:
|
122
|
+
- LICENSE
|
123
|
+
- README.md
|
124
|
+
files:
|
125
|
+
- Gemfile
|
126
|
+
- Gemfile.lock
|
127
|
+
- LICENSE
|
128
|
+
- README.md
|
129
|
+
- Rakefile
|
130
|
+
- VERSION
|
131
|
+
- VERSION.yml
|
132
|
+
- config/f1-oauth.yml
|
133
|
+
- lib/f1api.rb
|
134
|
+
- lib/f1api/activeresource/base.rb
|
135
|
+
- lib/f1api/activeresource/connection.rb
|
136
|
+
- lib/f1api/client.rb
|
137
|
+
- lib/f1api/configuration.rb
|
138
|
+
- lib/f1api/oauth.rb
|
139
|
+
- lib/f1api/oauth/credentials_authentication.rb
|
140
|
+
- lib/f1api/oauth/oauth_authentication.rb
|
141
|
+
- test/fixtures/access_token.rb
|
142
|
+
- test/fixtures/http.rb
|
143
|
+
- test/fixtures/request_token.rb
|
144
|
+
- test/unit/configuration_test.rb
|
145
|
+
- test/unit/credentials_test.rb
|
146
|
+
- test/unit/oauth_test.rb
|
147
|
+
has_rdoc: true
|
148
|
+
homepage: http://github.com/jessedearing/f1api
|
149
|
+
licenses:
|
150
|
+
- MIT
|
151
|
+
post_install_message:
|
152
|
+
rdoc_options: []
|
153
|
+
|
154
|
+
require_paths:
|
155
|
+
- lib
|
156
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
157
|
+
none: false
|
158
|
+
requirements:
|
159
|
+
- - ">="
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
segments:
|
162
|
+
- 0
|
163
|
+
version: "0"
|
164
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
165
|
+
none: false
|
166
|
+
requirements:
|
167
|
+
- - ">="
|
168
|
+
- !ruby/object:Gem::Version
|
169
|
+
segments:
|
170
|
+
- 0
|
171
|
+
version: "0"
|
172
|
+
requirements: []
|
173
|
+
|
174
|
+
rubyforge_project:
|
175
|
+
rubygems_version: 1.3.7
|
176
|
+
signing_key:
|
177
|
+
specification_version: 3
|
178
|
+
summary: Consume the Fellowship One API in your apps using ActiveResource
|
179
|
+
test_files:
|
180
|
+
- test/fixtures/access_token.rb
|
181
|
+
- test/fixtures/http.rb
|
182
|
+
- test/fixtures/request_token.rb
|
183
|
+
- test/unit/configuration_test.rb
|
184
|
+
- test/unit/credentials_test.rb
|
185
|
+
- test/unit/oauth_test.rb
|