checkpoint 0.2.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.envrc +1 -0
- data/.gitignore +18 -9
- data/.rspec +2 -0
- data/.rubocop.yml +30 -0
- data/.travis.yml +5 -0
- data/.yardopts +1 -0
- data/Gemfile +5 -1
- data/LICENSE.md +27 -0
- data/README.md +23 -0
- data/Rakefile +14 -0
- data/bin/console +18 -0
- data/bin/rake +21 -0
- data/bin/rspec +21 -0
- data/bin/sequel +21 -0
- data/bin/setup +8 -0
- data/bin/yard +21 -0
- data/bin/yardoc +21 -0
- data/checkpoint.gemspec +37 -19
- data/db/migrations/1_create_permits.rb +19 -0
- data/docs/Makefile +24 -0
- data/docs/_static/.gitkeep +0 -0
- data/docs/_templates/.gitkeep +0 -0
- data/docs/authentication.rst +18 -0
- data/docs/conf.py +46 -0
- data/docs/index.rst +28 -0
- data/docs/policies.rst +211 -0
- data/docs/requirements.txt +4 -0
- data/lib/checkpoint.rb +16 -2
- data/lib/checkpoint/agent.rb +93 -0
- data/lib/checkpoint/agent/resolver.rb +33 -0
- data/lib/checkpoint/agent/token.rb +52 -0
- data/lib/checkpoint/authority.rb +67 -0
- data/lib/checkpoint/credential.rb +82 -0
- data/lib/checkpoint/credential/permission.rb +27 -0
- data/lib/checkpoint/credential/resolver.rb +87 -0
- data/lib/checkpoint/credential/role.rb +26 -0
- data/lib/checkpoint/credential/token.rb +51 -0
- data/lib/checkpoint/db.rb +161 -0
- data/lib/checkpoint/db/permit.rb +24 -0
- data/lib/checkpoint/permission_mapper.rb +29 -0
- data/lib/checkpoint/permits.rb +133 -0
- data/lib/checkpoint/query.rb +42 -0
- data/lib/checkpoint/query/action_permitted.rb +40 -0
- data/lib/checkpoint/query/role_granted.rb +55 -0
- data/lib/checkpoint/railtie.rb +92 -71
- data/lib/checkpoint/resource.rb +138 -0
- data/lib/checkpoint/resource/all_of_any_type.rb +34 -0
- data/lib/checkpoint/resource/all_of_type.rb +50 -0
- data/lib/checkpoint/resource/any_entity.rb +25 -0
- data/lib/checkpoint/resource/any_entity_of_type.rb +29 -0
- data/lib/checkpoint/resource/resolver.rb +21 -0
- data/lib/checkpoint/resource/token.rb +65 -0
- data/lib/checkpoint/version.rb +3 -1
- data/lib/tasks/migrate.rake +75 -0
- metadata +260 -19
- data/Readme.markdown +0 -103
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Checkpoint
|
4
|
+
module DB
|
5
|
+
# Sequel model for permits
|
6
|
+
class Permit < Sequel::Model
|
7
|
+
# Instantiate a Permit from the constituent domain objects (agent,
|
8
|
+
# resource, credential).
|
9
|
+
def self.from(agent, credential, resource, zone: 'system')
|
10
|
+
new(
|
11
|
+
agent_type: agent.type, agent_id: agent.id, agent_token: agent.token,
|
12
|
+
credential_type: credential.type, credential_id: credential.id, credential_token: credential.token,
|
13
|
+
resource_type: resource.type, resource_id: resource.id, resource_token: resource.token,
|
14
|
+
zone_id: zone
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
# The default/system zone
|
19
|
+
def self.default_zone
|
20
|
+
'(all)'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Checkpoint
|
4
|
+
# A PermissionMapper translates an action into a set of permissions and roles
|
5
|
+
# that would allow it. Commonly, the actions and permissions will share names
|
6
|
+
# for convenience and consistency, but this is not a requirement.
|
7
|
+
#
|
8
|
+
# For example, it may make sense in an application that one permission
|
9
|
+
# implies another, so an action may have multiple permissions that would
|
10
|
+
# allow it. In another application, it may be more convenient and
|
11
|
+
# understandable for users to have separate roles encapsulate that concept
|
12
|
+
# (such as an editor role having all of the permissions of a reader role and
|
13
|
+
# more).
|
14
|
+
#
|
15
|
+
# As a separate example, it may be more appropriate to implement permission
|
16
|
+
# inheritance directly in policy code (as by delegating to another check or
|
17
|
+
# policy), relying on the matching action and permission names with no roles
|
18
|
+
# resolved, as given by the default PermissionMapper. Checkpoint does not
|
19
|
+
# take an absolute position on the best pattern for a given application.
|
20
|
+
class PermissionMapper
|
21
|
+
def permissions_for(action)
|
22
|
+
[action.to_sym]
|
23
|
+
end
|
24
|
+
|
25
|
+
def roles_granting(_action)
|
26
|
+
[]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Note: we do not require db/permit because Sequel requires the connection
|
4
|
+
# to be set up before defining the model classes. The arrangment here
|
5
|
+
# assumes that DB.initialize! will have been called if the default model
|
6
|
+
# is to be used. In tests, that is done by spec/sequel_helper.rb. In an
|
7
|
+
# application, there should be an initializer that reads whatever appropriate
|
8
|
+
# configuration and does the initialization.
|
9
|
+
|
10
|
+
require 'checkpoint/db'
|
11
|
+
|
12
|
+
module Checkpoint
|
13
|
+
# The repository of permits -- a simple wrapper for the Sequel Datastore / permits table.
|
14
|
+
class Permits
|
15
|
+
def initialize(permits: Checkpoint::DB::Permit)
|
16
|
+
@permits = permits
|
17
|
+
end
|
18
|
+
|
19
|
+
def for(agents, credentials, resources)
|
20
|
+
where(agents, credentials, resources).select
|
21
|
+
end
|
22
|
+
|
23
|
+
def any?(agents, credentials, resources)
|
24
|
+
where(agents, credentials, resources).first != nil
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def where(agents, credentials, resources)
|
30
|
+
Query.new(agents, credentials, resources, scope: permits)
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_reader :permits
|
34
|
+
|
35
|
+
# A query object based on agents, credentials, and resources.
|
36
|
+
#
|
37
|
+
# This is a helper to capture a set of agents, credentials, and resources,
|
38
|
+
# and manage assembly of placeholder variables and binding expressions in
|
39
|
+
# the way Sequel expects them. It can take single items or arrays and
|
40
|
+
# converts them all to their tokens for query purposes.
|
41
|
+
class Query
|
42
|
+
attr_reader :agents, :credentials, :resources, :scope
|
43
|
+
|
44
|
+
def initialize(agents, credentials, resources, scope: Checkpoint::DB::Permit)
|
45
|
+
@agents = tokenize(agents)
|
46
|
+
@credentials = tokenize(credentials)
|
47
|
+
@resources = tokenize(resources)
|
48
|
+
@scope = scope
|
49
|
+
end
|
50
|
+
|
51
|
+
def query
|
52
|
+
scope.where(conditions)
|
53
|
+
end
|
54
|
+
|
55
|
+
def select
|
56
|
+
exec(:select)
|
57
|
+
end
|
58
|
+
|
59
|
+
def first
|
60
|
+
exec(:first)
|
61
|
+
end
|
62
|
+
|
63
|
+
def conditions
|
64
|
+
{
|
65
|
+
agent_token: agent_params.placeholders,
|
66
|
+
credential_token: credential_params.placeholders,
|
67
|
+
resource_token: resource_params.placeholders,
|
68
|
+
zone_id: :$zone_id
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
def parameters
|
73
|
+
(agent_params.values +
|
74
|
+
credential_params.values +
|
75
|
+
resource_params.values +
|
76
|
+
[[:zone_id, DB::Permit.default_zone]]).to_h
|
77
|
+
end
|
78
|
+
|
79
|
+
def agent_params
|
80
|
+
Params.new(agents, 'at')
|
81
|
+
end
|
82
|
+
|
83
|
+
def credential_params
|
84
|
+
Params.new(credentials, 'ct')
|
85
|
+
end
|
86
|
+
|
87
|
+
def resource_params
|
88
|
+
Params.new(resources, 'rt')
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def exec(mode)
|
94
|
+
query.call(mode, parameters)
|
95
|
+
end
|
96
|
+
|
97
|
+
def tokenize(collection)
|
98
|
+
[collection].flatten.map(&:token)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# A helper for building placeholder variable names from items in a list and
|
103
|
+
# providing a corresponding hash of values. A prefix with some mnemonic
|
104
|
+
# corresponding to the column is recommended. For example, if the column is
|
105
|
+
# `agent_token`, using the prefix `at` will yield `$at_0`, `$at_1`, etc. for
|
106
|
+
# an IN clause.
|
107
|
+
class Params
|
108
|
+
attr_reader :items, :prefix
|
109
|
+
|
110
|
+
def initialize(items, prefix)
|
111
|
+
@items = [items].flatten
|
112
|
+
@prefix = prefix
|
113
|
+
end
|
114
|
+
|
115
|
+
def placeholders
|
116
|
+
0.upto(items.size - 1).map do |i|
|
117
|
+
:"$#{prefix}_#{i}"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def values
|
122
|
+
items.map.with_index do |item, i|
|
123
|
+
value = if item.respond_to?(:sql_value)
|
124
|
+
item.sql_value
|
125
|
+
else
|
126
|
+
item.to_s
|
127
|
+
end
|
128
|
+
[:"#{prefix}_#{i}", value]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'checkpoint/query/role_granted'
|
4
|
+
require 'checkpoint/query/action_permitted'
|
5
|
+
|
6
|
+
module Checkpoint
|
7
|
+
# The Query module is a container for the various types of checks or
|
8
|
+
# inquiries that an application might want to make.
|
9
|
+
#
|
10
|
+
# These classes provide a more expressive and object-oriented pattern than
|
11
|
+
# scattering the primitives and throughout the framework (and, more
|
12
|
+
# importantly, application) code base. They improve consistency and
|
13
|
+
# ergonomics in a similar way as named queries or scopes on a model class.
|
14
|
+
# That is, it's possible to query the authority directly (or model, by
|
15
|
+
# comparison) with primitives, but these classes will capture the semantics
|
16
|
+
# of a particular check, taking the conceptually pertinent parameters, and
|
17
|
+
# applying any defaults or conversion to authoriziation primitives needed,
|
18
|
+
# particularly around credential types.
|
19
|
+
#
|
20
|
+
# Despite modeling the semantics of a query in a convenient way, these
|
21
|
+
# objects do not assume a singleton authority. To make their usage truly
|
22
|
+
# convenient, they should be created from a factory method that binds them to
|
23
|
+
# an already-configured {Checkpoint::Authority}.
|
24
|
+
#
|
25
|
+
# NOTE: @botimer 2018-02-25: I suspect that we will build a convenience class
|
26
|
+
# that binds an authority, and has a factory method per query. This might end
|
27
|
+
# up being the main interface to Checkpoint; a wide-but-shallow adapter
|
28
|
+
# object that can be set up at initialization and made available to
|
29
|
+
# application policies (rather than using the authority directly). I also
|
30
|
+
# suspect that a shorthand adapter will appear for convenient aliasing in
|
31
|
+
# context. For example, a `can?` method on a base application policy that
|
32
|
+
# requires only an action parameter, binding its user and resource by
|
33
|
+
# default, would be a familiar and ergonomic way to call `action_permitted`.
|
34
|
+
# This would create and evaluate a new ActionPermitted instance bound to the
|
35
|
+
# parameters and the configured authority. A pattern like this would achieve
|
36
|
+
# the concurrent goals of maintaining the framework design and call-site
|
37
|
+
# simplicity, without relying on mixins -- the delegation to Checkpoint would
|
38
|
+
# be made explicit with some short boilerplate in application code that can
|
39
|
+
# be found and examined without digging into gems.
|
40
|
+
module Query
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Checkpoint
|
4
|
+
module Query
|
5
|
+
# ActionPermitted is a predicate query that captures the user, action,
|
6
|
+
# and target, and checks if the authority permits the action. It is likely
|
7
|
+
# to be the most commonly issued query in any given application.
|
8
|
+
class ActionPermitted
|
9
|
+
attr_reader :user, :action, :target
|
10
|
+
|
11
|
+
# @param user [<application actor>] the acting user/account
|
12
|
+
# @param action [String|Symbol] the action to be taken; this will be
|
13
|
+
# forced to a symbol
|
14
|
+
# @param target [<application entity>] the object or application resource
|
15
|
+
# to be acted upon; defaults to {Checkpoint::Resource.all} to ease
|
16
|
+
# checking for zone-/system-wide permission.
|
17
|
+
# @param authority [Checkpoint::Authority] the authority to ask about
|
18
|
+
# this permission
|
19
|
+
def initialize(
|
20
|
+
user,
|
21
|
+
action,
|
22
|
+
target = Checkpoint::Resource.all,
|
23
|
+
authority: Authority::RejectAll.new)
|
24
|
+
|
25
|
+
@user = user
|
26
|
+
@action = action.to_sym
|
27
|
+
@target = target
|
28
|
+
@authority = authority
|
29
|
+
end
|
30
|
+
|
31
|
+
def true?
|
32
|
+
authority.permits?(user, action, target)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
attr_reader :authority
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Checkpoint
|
4
|
+
module Query
|
5
|
+
# RoleGranted is a predicate query that captures the user, role, and
|
6
|
+
# target, and checks if the authority recognizes the user as having the
|
7
|
+
# role.
|
8
|
+
#
|
9
|
+
# TODO: Extract-To-Manual
|
10
|
+
# There are two primary approaches to handling which actions are permitted
|
11
|
+
# for which roles:
|
12
|
+
#
|
13
|
+
# 1. Encoding the details directly in policy objects and checking for the
|
14
|
+
# appropriate roles within a given rule. This has the effect of placing
|
15
|
+
# the literal values within the body of a rule, making it quite easy
|
16
|
+
# to examine. Tests can validate system behavior at development time
|
17
|
+
# because it is static.
|
18
|
+
#
|
19
|
+
# 2. Implementing a {Checkpoint::Credential::Mapper} that maps backward
|
20
|
+
# from actions to named permissions and roles that would allow them.
|
21
|
+
# The policy rules would only authorize actions, leaving the mapping
|
22
|
+
# outside to accommodate configuration or runtime modification. This has
|
23
|
+
# the effect of being more flexible, while making the specifics of a
|
24
|
+
# rule more difficult to examine. Tests can only validate system
|
25
|
+
# behavior for a particular configuration -- whether an instance of the
|
26
|
+
# application is configured in a correct or expected way is not testable
|
27
|
+
# at development time.
|
28
|
+
class RoleGranted
|
29
|
+
attr_reader :user, :role, :target
|
30
|
+
|
31
|
+
# @param user [<application actor>] the acting user/account
|
32
|
+
# @param role [String|Symbol] the role to be checked; this will be
|
33
|
+
# forced to a symbol
|
34
|
+
# @param target [<application entity>] the object or application resource
|
35
|
+
# for which the user may have a role; defaults to {Checkpoint::Resource.all}
|
36
|
+
# to ease checking for zone-/system-wide roles.
|
37
|
+
# @param authority [Checkpoint::Authority] the authority to ask about
|
38
|
+
# this role-grant
|
39
|
+
def initialize(user, role, target = Resource.all, authority: Authority::RejectAll.new)
|
40
|
+
@user = user
|
41
|
+
@role = role.to_sym
|
42
|
+
@target = target
|
43
|
+
@authority = authority
|
44
|
+
end
|
45
|
+
|
46
|
+
def true?
|
47
|
+
authority.permits?(user, Credential::Role.new(role), target)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
attr_reader :authority
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/checkpoint/railtie.rb
CHANGED
@@ -1,84 +1,105 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
3
|
+
module Checkpoint
|
4
|
+
# Railtie to hook Checkpoint into Rails applications.
|
5
|
+
#
|
6
|
+
# This does three things at present:
|
7
|
+
#
|
8
|
+
# 1. Loads our rake tasks, so you can run checkpoint:migrate from the app.
|
9
|
+
# 2. Pulls the Rails database information off of the ActiveRecord
|
10
|
+
# connection and puts it on Checkpoint::DB.config before any application
|
11
|
+
# initializers are run.
|
12
|
+
# 3. Sets up the Checkpoint database connection after application
|
13
|
+
# initializers have run, if it has not already been done and we are not
|
14
|
+
# running as a Rake task. This condition is key because when we are in
|
15
|
+
# rails server or console, we want to initialize!, but when we are in
|
16
|
+
# a rake task to update the database, we have to let it connect, but
|
17
|
+
# not initialize.
|
18
|
+
class Railtie < Rails::Railtie
|
19
|
+
railtie_name :checkpoint
|
20
|
+
|
21
|
+
class << self
|
22
|
+
# Register a callback to run before anything in 'config/initializers' runs.
|
23
|
+
# The block will get a reference to Checkpoint::DB.config as its only parameter.
|
24
|
+
def before_initializers(&block)
|
25
|
+
before_blocks << block
|
12
26
|
end
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
to_regexp = lambda do |pattern|
|
21
|
-
if arg1.class.to_s == 'Regexp'
|
22
|
-
arg1
|
23
|
-
else
|
24
|
-
Regexp.new('\A' + pattern.to_s.gsub(/[^\*]/){|char| Regexp.quote(char)}.gsub(/\*/){|| ".*?"} + '\Z')
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
patterns = []
|
29
|
-
if arg1.class.to_s == 'Array'
|
30
|
-
arg1.each {|pattern| patterns.push to_regexp.call(pattern) }
|
31
|
-
else
|
32
|
-
patterns.push to_regexp.call(arg1)
|
33
|
-
end
|
34
|
-
|
35
|
-
authorise_controllers_blocks = ::ApplicationController.authorise_controllers_blocks
|
36
|
-
|
37
|
-
patterns.each do |pattern|
|
38
|
-
if authorise_controllers_blocks [pattern].nil?
|
39
|
-
authorise_controllers_blocks[pattern] = []
|
40
|
-
end
|
41
|
-
authorise_controllers_blocks[pattern].push(block)
|
42
|
-
end
|
27
|
+
|
28
|
+
# Register a callback to run after anything in 'config/initializers' runs.
|
29
|
+
# The block will get a reference to Checkpoint::DB.config as its only parameter.
|
30
|
+
# Checkpoint::DB.initialize! will not have been automatically called at this
|
31
|
+
# point, so this is an opportunity to do so if an initializer has not.
|
32
|
+
def after_initializers(&block)
|
33
|
+
after_blocks << block
|
43
34
|
end
|
44
|
-
|
45
|
-
#
|
46
|
-
|
47
|
-
|
35
|
+
|
36
|
+
# Register a callback to run when Checkpoint is ready and fully initialized.
|
37
|
+
# This will happen once in production, and on each request in development.
|
38
|
+
# If you need to do something once in development, you can choose between
|
39
|
+
# keeping a flag or using the after_initializers.
|
40
|
+
def when_checkpoint_is_ready(&block)
|
41
|
+
ready_blocks << block
|
48
42
|
end
|
49
|
-
|
50
|
-
def
|
51
|
-
|
52
|
-
::ApplicationController.authorise_controllers_blocks.each do |pattern, blocks|
|
53
|
-
if action.match pattern
|
54
|
-
blocks.each do |block|
|
55
|
-
if instance_eval(&block)
|
56
|
-
return true
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
false
|
43
|
+
|
44
|
+
def before_blocks
|
45
|
+
@before ||= []
|
62
46
|
end
|
63
47
|
|
64
|
-
def
|
65
|
-
|
66
|
-
logger.info " (401) Access Denied!"
|
67
|
-
logger.info " * see the above request for more info"
|
68
|
-
logger.info "-----------------------------------------------\n\n"
|
69
|
-
render :text => "Access Denied", :status => 401
|
48
|
+
def after_blocks
|
49
|
+
@after ||= []
|
70
50
|
end
|
71
51
|
|
72
|
-
def
|
73
|
-
|
74
|
-
access_denied
|
75
|
-
end
|
52
|
+
def ready_blocks
|
53
|
+
@ready ||= []
|
76
54
|
end
|
77
|
-
|
78
|
-
|
79
|
-
|
55
|
+
|
56
|
+
def under_rake!
|
57
|
+
@rake = true
|
80
58
|
end
|
81
|
-
|
59
|
+
|
60
|
+
def under_rake?
|
61
|
+
@rake ||= false
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# This runs before anything in 'config/initializers' runs.
|
66
|
+
initializer "checkpoint.before_initializers", before: :load_config_initializers do
|
67
|
+
config = Checkpoint::DB.config
|
68
|
+
unless config.url
|
69
|
+
opts = ActiveRecord::Base.connection.instance_variable_get(:@config).dup
|
70
|
+
opts.delete(:flags)
|
71
|
+
config[:opts] = opts
|
72
|
+
end
|
73
|
+
|
74
|
+
Railtie.before_blocks.each do |block|
|
75
|
+
block.call(config.to_h)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# This runs after everything in 'config/initializers' runs.
|
80
|
+
initializer "checkpoint.after_initializers", after: :load_config_initializers do
|
81
|
+
config = Checkpoint::DB.config
|
82
|
+
Railtie.after_blocks.each do |block|
|
83
|
+
block.call(config.to_h)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# This runs before any block registered under a `config.to_prepare`, which
|
88
|
+
# could be in plugins or initializers that want to use a fully configured
|
89
|
+
# Checkpoint instance. The `to_prepare` hook is run once at the start of a
|
90
|
+
# production instance and for every request in development (unless caching
|
91
|
+
# is turned on so there is no reloading).
|
92
|
+
initializer "checkpoint.ready", after: :finisher_hook do
|
93
|
+
Checkpoint::DB.initialize! unless Railtie.under_rake?
|
94
|
+
|
95
|
+
Railtie.ready_blocks.each do |block|
|
96
|
+
block.call(Checkpoint::DB.db)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
rake_tasks do
|
101
|
+
Railtie.under_rake!
|
102
|
+
load "tasks/migrate.rake"
|
82
103
|
end
|
83
104
|
end
|
84
105
|
end
|