ey_gatekeeper 0.1.34
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/lib/ey_gatekeeper/access_control_list.rb +133 -0
- data/lib/ey_gatekeeper/client/consumer.rb +63 -0
- data/lib/ey_gatekeeper/client/middlewares/authentication.rb +77 -0
- data/lib/ey_gatekeeper/client/middlewares/service_token_authentication.rb +39 -0
- data/lib/ey_gatekeeper/client/server_response.rb +37 -0
- data/lib/ey_gatekeeper/client.rb +44 -0
- data/lib/ey_gatekeeper/responses.rb +67 -0
- data/lib/ey_gatekeeper/token.rb +29 -0
- data/lib/ey_gatekeeper/util.rb +23 -0
- data/lib/ey_gatekeeper/version.rb +5 -0
- data/lib/ey_gatekeeper.rb +54 -0
- data/spec/access_control_list_spec.rb +192 -0
- data/spec/auth_service_spec.rb +332 -0
- data/spec/consumer_spec.rb +80 -0
- data/spec/fallback_spec.rb +76 -0
- data/spec/impersonation_spec.rb +50 -0
- data/spec/responses_spec.rb +23 -0
- data/spec/service_token_authentication_spec.rb +10 -0
- data/spec/spec_helper.rb +133 -0
- metadata +129 -0
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'cgi'
|
3
|
+
|
4
|
+
module EY
|
5
|
+
module GateKeeper
|
6
|
+
|
7
|
+
# An individual control, probably within a list.
|
8
|
+
# Example: AccessControl.new('xdna://foobars', ['GET', 'PUT', 'DELETE'])
|
9
|
+
class AccessControl
|
10
|
+
attr_reader :path, :methods
|
11
|
+
|
12
|
+
def initialize(path, *methods)
|
13
|
+
@path, @methods = path, methods.flatten.map {|m| m.to_s.upcase }
|
14
|
+
end
|
15
|
+
|
16
|
+
def allow?(method)
|
17
|
+
methods.include?(method.to_s.upcase)
|
18
|
+
end
|
19
|
+
|
20
|
+
def suitable_for?(test_path)
|
21
|
+
test_path = URI.escape(URI.unescape(test_path))
|
22
|
+
test_path_uri = URI.parse(test_path)
|
23
|
+
|
24
|
+
matches?(test_path_uri) && parameters_satisfied?(test_path_uri)
|
25
|
+
end
|
26
|
+
|
27
|
+
def matches?(test_path_uri)
|
28
|
+
path_without_parameters == 'xdna://' ||
|
29
|
+
"xdna:/#{test_path_uri.path}".match(%r{^#{path_without_parameters}(\/|$)})
|
30
|
+
end
|
31
|
+
|
32
|
+
def parameters_satisfied?(test_path_uri)
|
33
|
+
(required_parameters - CGI.parse(test_path_uri.query || '').keys).empty?
|
34
|
+
end
|
35
|
+
|
36
|
+
def required_parameters
|
37
|
+
@required_parameters ||= CGI.parse(query || '').keys
|
38
|
+
end
|
39
|
+
|
40
|
+
# can't use URI for these right now since it barfs on _ in the
|
41
|
+
# hostname portion
|
42
|
+
def path_without_parameters
|
43
|
+
path.split('?').first
|
44
|
+
end
|
45
|
+
|
46
|
+
def query
|
47
|
+
path.split('?', 2)[1]
|
48
|
+
end
|
49
|
+
|
50
|
+
# Intersect 2 acccess controls
|
51
|
+
def &(other_control)
|
52
|
+
AccessControl.new(path, other_control.methods & methods)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# A list of AccessControl objects. Can check a request method and path against all controls in the list.
|
57
|
+
# Example: AccessControlList.new({ 'xdna://foobars' => ['GET', 'PUT', 'DELETE'], 'xdna://foobazes' => [] })
|
58
|
+
class AccessControlList
|
59
|
+
attr_reader :list
|
60
|
+
|
61
|
+
def initialize(list)
|
62
|
+
@list = case list
|
63
|
+
when Hash
|
64
|
+
list.map {|path, methods| AccessControl.new(path, methods) }
|
65
|
+
when Array
|
66
|
+
list
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def allow?(method, path)
|
71
|
+
control = control_for(path)
|
72
|
+
control && control.allow?(method.to_s.upcase)
|
73
|
+
end
|
74
|
+
|
75
|
+
def deny?(method, path)
|
76
|
+
!allow?(method, path)
|
77
|
+
end
|
78
|
+
|
79
|
+
def control_for(path)
|
80
|
+
matches = list.find_all {|c| c.suitable_for?(path) }
|
81
|
+
matches.sort_by {|c| c.path.size }.last
|
82
|
+
end
|
83
|
+
|
84
|
+
def paths
|
85
|
+
list.map {|c| c.path }
|
86
|
+
end
|
87
|
+
|
88
|
+
def [](path)
|
89
|
+
list.detect {|c| c.path == path }
|
90
|
+
end
|
91
|
+
|
92
|
+
# Intersect the two lists
|
93
|
+
def &(other_list)
|
94
|
+
new_list = (paths + other_list.paths).uniq.map do |path|
|
95
|
+
if self[path] && other_list[path]
|
96
|
+
# If both lists have the path, do a simple control AND
|
97
|
+
self[path] & other_list[path]
|
98
|
+
elsif self[path]
|
99
|
+
# If only one side has the path, see if there's a less specific control we can AND with
|
100
|
+
pare_down(self[path], other_list)
|
101
|
+
elsif other_list[path]
|
102
|
+
# Same for here, just opposite
|
103
|
+
pare_down(other_list[path], self)
|
104
|
+
end
|
105
|
+
end.compact
|
106
|
+
|
107
|
+
AccessControlList.new(new_list)
|
108
|
+
end
|
109
|
+
|
110
|
+
def to_hash
|
111
|
+
{}.tap do |result|
|
112
|
+
list.each do |control|
|
113
|
+
result.update(control.path => control.methods)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def pare_down(target, possibilities)
|
121
|
+
# Find a less specific control with the longest path
|
122
|
+
# starting with target.path. We can then use that control for
|
123
|
+
# performing an AND. This covers the xdna:// vs. xdna://foos
|
124
|
+
# case.
|
125
|
+
most_specific_less_specific_control = possibilities.list.
|
126
|
+
find_all {|p| p.path == 'xdna://' || target.path =~ %r{^#{p.path}(\/|$)} }.sort_by {|p| p.path.size }.last
|
127
|
+
return unless most_specific_less_specific_control
|
128
|
+
|
129
|
+
target & most_specific_less_specific_control
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module EY
|
2
|
+
module GateKeeper
|
3
|
+
module Client
|
4
|
+
class Consumer < Rack::Client::Simple
|
5
|
+
attr_accessor :token, :key, :secret, :impersonation_token
|
6
|
+
|
7
|
+
def initialize(key, secret, options = {})
|
8
|
+
@key = key
|
9
|
+
@secret = secret
|
10
|
+
@endpoint = options[:endpoint]
|
11
|
+
@token = nil
|
12
|
+
|
13
|
+
if @endpoint
|
14
|
+
super(build_app(options), @endpoint)
|
15
|
+
else
|
16
|
+
super(build_app(options))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def token
|
21
|
+
get('/cloudkit-meta')
|
22
|
+
@token
|
23
|
+
end
|
24
|
+
|
25
|
+
def impersonate!(impersonation_token)
|
26
|
+
@impersonation_token = case impersonation_token
|
27
|
+
when EY::GateKeeper::Token
|
28
|
+
impersonation_token.token
|
29
|
+
when String, NilClass
|
30
|
+
impersonation_token
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def just_me!
|
35
|
+
impersonate!(nil)
|
36
|
+
end
|
37
|
+
|
38
|
+
def effective_access_controls
|
39
|
+
response = get('/_effective_access_controls')
|
40
|
+
if response.status == 200 && response.content_type == 'application/json'
|
41
|
+
JSON.parse(response.body) # rack-client collapses the body for us
|
42
|
+
else
|
43
|
+
{}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def build_app(options)
|
48
|
+
inner_app = options.fetch(:app, Rack::Client::Handler::NetHTTP.new)
|
49
|
+
token_setter = method(:token=)
|
50
|
+
key_method = method(:key)
|
51
|
+
secret_method = method(:secret)
|
52
|
+
impersonation_token_method = method(:impersonation_token)
|
53
|
+
|
54
|
+
Rack::Builder.app do |builder|
|
55
|
+
builder.use EY::GateKeeper::Client::Middleware::Authentication, key_method, secret_method, token_setter, impersonation_token_method
|
56
|
+
builder.run inner_app
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module EY
|
2
|
+
module GateKeeper
|
3
|
+
module Client
|
4
|
+
module Middleware
|
5
|
+
class Authentication
|
6
|
+
|
7
|
+
def initialize(app, key_callback, secret_callback, token_setter, impersonation_token_callback)
|
8
|
+
@app, @key_callback, @secret_callback, @token_setter, @impersonation_token_callback =
|
9
|
+
app, key_callback, secret_callback, token_setter, impersonation_token_callback
|
10
|
+
end
|
11
|
+
|
12
|
+
def need_token?
|
13
|
+
@token.nil? || @token.expired?
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_token_to(env)
|
17
|
+
if @token
|
18
|
+
env.update('HTTP_AUTH_TOKEN' => @token.token)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_impersonation_token_to(env)
|
23
|
+
impersonation_token = @impersonation_token_callback.call
|
24
|
+
if impersonation_token
|
25
|
+
env.update('HTTP_X_IMPERSONATION_TOKEN' => impersonation_token)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def call(env)
|
30
|
+
if need_token?
|
31
|
+
get_token(env.dup)
|
32
|
+
end
|
33
|
+
|
34
|
+
add_token_to(env)
|
35
|
+
add_impersonation_token_to(env)
|
36
|
+
|
37
|
+
app_response = EY::GateKeeper::Client::ServerResponse.new(@app.call(env))
|
38
|
+
return app_response unless @token
|
39
|
+
|
40
|
+
if app_response.needs_gatekeeper_token_refresh?
|
41
|
+
get_token(env.dup)
|
42
|
+
add_token_to(env)
|
43
|
+
app_response = EY::GateKeeper::Client::ServerResponse.new(@app.call(env))
|
44
|
+
end
|
45
|
+
|
46
|
+
app_response.to_rack
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_token(env)
|
50
|
+
if auth_response = authenticate(env)
|
51
|
+
@token = EY::GateKeeper::Token.new(auth_response)
|
52
|
+
else
|
53
|
+
@token = nil
|
54
|
+
end
|
55
|
+
@token_setter.call(@token) if @token_setter
|
56
|
+
end
|
57
|
+
|
58
|
+
def authenticate(env)
|
59
|
+
key = @key_callback.call
|
60
|
+
secret = @secret_callback.call
|
61
|
+
|
62
|
+
env.update('HTTP_AUTHORIZATION' => ["#{key}:#{secret}"].pack("m*").gsub("\n", ""))
|
63
|
+
env.update('REQUEST_PATH' => '/_authenticate')
|
64
|
+
env.update('PATH_INFO' => '/_authenticate')
|
65
|
+
|
66
|
+
auth_response = EY::GateKeeper::Client::ServerResponse.new(@app.call(env))
|
67
|
+
|
68
|
+
if auth_response.status == 200
|
69
|
+
JSON.parse(auth_response.body.join)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module EY
|
2
|
+
module GateKeeper
|
3
|
+
module Client
|
4
|
+
module Middleware
|
5
|
+
# This middleware is for use by service clients.
|
6
|
+
#
|
7
|
+
# Given the service client's app to run along with a
|
8
|
+
# gatekeeper endpoint, key, and secret it will contact the
|
9
|
+
# gatekeeper and retrieve a token. The token will then be
|
10
|
+
# passed as part of the request in the `Auth-Token` header.
|
11
|
+
# @param app The service client's Rack::Client application (passed automatically when this middleware is `use`d
|
12
|
+
# @param [String] gatekeeper_endpoint URL of the gatekeeper to fetch the token from
|
13
|
+
# @param [String] xdna_key Xdna key to authenticate against the gatekeeper with
|
14
|
+
# @param [String] xdna_secret Xdna secret to authenticate against the gatekeeper with
|
15
|
+
class ServiceTokenAuthentication
|
16
|
+
|
17
|
+
def initialize(app, gatekeeper_endpoint, xdna_key, xdna_secret)
|
18
|
+
@app, @gatekeeper_endpoint, @xdna_key, @xdna_secret =
|
19
|
+
app, gatekeeper_endpoint, xdna_key, xdna_secret
|
20
|
+
end
|
21
|
+
|
22
|
+
def call(env)
|
23
|
+
env.merge!('HTTP_AUTH_TOKEN' => current_token.token)
|
24
|
+
@app.call(env)
|
25
|
+
end
|
26
|
+
|
27
|
+
def gatekeeper_client
|
28
|
+
@gatekeeper_client ||= EY::GateKeeper.new(@xdna_key, @xdna_secret, :endpoint => @gatekeeper_endpoint)
|
29
|
+
end
|
30
|
+
|
31
|
+
def current_token
|
32
|
+
gatekeeper_client.token
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module EY
|
2
|
+
module GateKeeper
|
3
|
+
module Client
|
4
|
+
class ServerResponse < Rack::Response
|
5
|
+
|
6
|
+
def initialize(response)
|
7
|
+
status, headers, body = response
|
8
|
+
super(body, status, headers)
|
9
|
+
end
|
10
|
+
|
11
|
+
def unauthorized?
|
12
|
+
status == 401
|
13
|
+
end
|
14
|
+
|
15
|
+
def is_gatekeeper_unauthorized?
|
16
|
+
(unauthorized? && headers['X-Gatekeeper-Authentication-Error']) ? true : false
|
17
|
+
end
|
18
|
+
|
19
|
+
def indicates_any_auth_error?(*messages)
|
20
|
+
if auth_error_header = headers['X-Gatekeeper-Authentication-Error']
|
21
|
+
messages.map { |message| EY::GateKeeper::Responses.string(message) }.include?(auth_error_header)
|
22
|
+
else
|
23
|
+
false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def needs_gatekeeper_token_refresh?
|
28
|
+
is_gatekeeper_unauthorized? && indicates_any_auth_error?(:expired_token, :expired_impersonation_token)
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_rack
|
32
|
+
[status, headers, body]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'rack/client'
|
2
|
+
|
3
|
+
module EY
|
4
|
+
module GateKeeper
|
5
|
+
module Client
|
6
|
+
|
7
|
+
def self.master_key
|
8
|
+
'77zxcvbnm77'
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.master_secret
|
12
|
+
'77zxcvbnm77'
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.master_user
|
16
|
+
'xcloud'
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.new(*a)
|
20
|
+
EY::GateKeeper::Client::Consumer.new(*a)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.mock(key = self.master_key, secret = self.master_secret, options = {})
|
24
|
+
mock_options = {
|
25
|
+
:endpoint => 'http://gatekeeper.local/',
|
26
|
+
:app => mock_app(master_key, master_secret, master_user)
|
27
|
+
}
|
28
|
+
|
29
|
+
new(key, secret, mock_options.merge(options))
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.mock_app(master_key = self.master_key, master_secret = self.master_secret, master_user = self.master_user)
|
33
|
+
Rack::Builder.app do |builder|
|
34
|
+
builder.run EY::GateKeeper::Server.app(:master_key => master_key, :master_secret => master_secret, :master_user => master_user)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
require 'ey_gatekeeper/client/middlewares/authentication'
|
42
|
+
require 'ey_gatekeeper/client/middlewares/service_token_authentication'
|
43
|
+
require 'ey_gatekeeper/client/server_response'
|
44
|
+
require 'ey_gatekeeper/client/consumer'
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module EY
|
2
|
+
module GateKeeper
|
3
|
+
module Responses
|
4
|
+
|
5
|
+
#The canned responses
|
6
|
+
CANNED_RESPONSES = {
|
7
|
+
:unable_to_create_token => 'unable to create token',
|
8
|
+
:invalid_authentication => 'invalid authentication info',
|
9
|
+
:no_authentication => 'no authentication info',
|
10
|
+
:no_token => 'no token specified',
|
11
|
+
:invalid_token => 'invalid token specified',
|
12
|
+
:expired_token => 'expired token specified',
|
13
|
+
:token_insufficient => 'token has insufficient access',
|
14
|
+
:invalid_impersonation_token => 'invalid impersonation token specified',
|
15
|
+
:expired_impersonation_token => 'expired impersonation token specified',
|
16
|
+
:impersonation_token_insufficient => 'impersonation token has insufficient access'
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
#Get the response string via symbol
|
20
|
+
def self.string(lookup = nil)
|
21
|
+
CANNED_RESPONSES.fetch(lookup,'')
|
22
|
+
end
|
23
|
+
|
24
|
+
#A list of possible response symbols
|
25
|
+
def self.possible_responses
|
26
|
+
CANNED_RESPONSES.keys
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
#Standard Unauthorized rsponse. Just add message
|
31
|
+
# @param [String] message the messsage to add into the response
|
32
|
+
#
|
33
|
+
# @return [Array] a standard Rack 3 member array response. The Headers contain the message provided
|
34
|
+
def unauthorized_response(message)
|
35
|
+
[
|
36
|
+
401,
|
37
|
+
{
|
38
|
+
'Content-Type' => 'text/plain',
|
39
|
+
'Content-Length' => '0',
|
40
|
+
'X-Gatekeeper-Authentication-Error' => message.is_a?(Symbol) ? EY::GateKeeper::Responses.string(message) : message.to_s
|
41
|
+
},
|
42
|
+
[]
|
43
|
+
]
|
44
|
+
end
|
45
|
+
|
46
|
+
#Standard token response
|
47
|
+
# @param [Hash] token the hash of the token data
|
48
|
+
# The response body is a json encoded hash and contains the token (keyed as 'token') and it's expiration (keyed as 'expires')
|
49
|
+
#
|
50
|
+
# @return [Array] a standard Rack 3 member array response
|
51
|
+
def token_response(token)
|
52
|
+
response = {
|
53
|
+
'token' => token['uri'].split("/")[2],
|
54
|
+
'expires' => token['expires']
|
55
|
+
}.to_json
|
56
|
+
[
|
57
|
+
200,
|
58
|
+
{
|
59
|
+
'Content-Type' => 'application/json',
|
60
|
+
'Content-Length' => response.length.to_s,
|
61
|
+
},
|
62
|
+
[response]
|
63
|
+
]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module EY
|
2
|
+
module GateKeeper
|
3
|
+
class Token
|
4
|
+
|
5
|
+
attr_reader :token_data
|
6
|
+
|
7
|
+
def initialize(token_data)
|
8
|
+
@token_data = token_data
|
9
|
+
end
|
10
|
+
|
11
|
+
def expired?
|
12
|
+
if @token_data['expires']
|
13
|
+
Time.now.utc.to_i >= @token_data['expires'].to_i
|
14
|
+
else
|
15
|
+
true
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def token
|
20
|
+
@token_data['token']
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
token
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module EY
|
2
|
+
module GateKeeper
|
3
|
+
module Util
|
4
|
+
|
5
|
+
if ENV['DEBUG_GATEKEEPER']
|
6
|
+
def debug(message)
|
7
|
+
puts message
|
8
|
+
end
|
9
|
+
else
|
10
|
+
def debug(message); end
|
11
|
+
end
|
12
|
+
|
13
|
+
def random_sha
|
14
|
+
Digest::SHA256.hexdigest((1..1000).map { |i| rand.to_s }.join)
|
15
|
+
end
|
16
|
+
|
17
|
+
def root_or_meta?(env)
|
18
|
+
env['PATH_INFO'] == '/' || env['PATH_INFO'] == '/cloudkit-meta'
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module EY
|
2
|
+
module GateKeeper
|
3
|
+
def self.new(*a)
|
4
|
+
if mocking?
|
5
|
+
EY::GateKeeper::Client.mock(*a)
|
6
|
+
else
|
7
|
+
EY::GateKeeper::Client.new(*a)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.mock!
|
12
|
+
@mocking = true
|
13
|
+
|
14
|
+
require 'ey_gatekeeper/server'
|
15
|
+
EY::GateKeeper::Server.mock!
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.mocking?
|
19
|
+
@mocking
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.needs_reset?
|
23
|
+
!!@needs_reset
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.was_reset!
|
27
|
+
@needs_reset = false
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.reset!(config = default_mongodb)
|
31
|
+
if mocking?
|
32
|
+
CloudKit.setup_storage_adapter(CloudKit::MemoryTable.new)
|
33
|
+
EY::GateKeeper::AuthStore.setup_master_credentials('77zxcvbnm77','77zxcvbnm77')
|
34
|
+
else
|
35
|
+
raise "Don't use reset! when not mocking"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.default_mongodb
|
40
|
+
{
|
41
|
+
:hosts => [["localhost", 27017]],
|
42
|
+
:safe_write_options => {
|
43
|
+
:fsync => false
|
44
|
+
}
|
45
|
+
}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
require 'ey_gatekeeper/util'
|
51
|
+
require 'ey_gatekeeper/responses'
|
52
|
+
require 'ey_gatekeeper/token'
|
53
|
+
require 'ey_gatekeeper/version'
|
54
|
+
require 'ey_gatekeeper/client'
|