the_garage 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +162 -0
- data/Rakefile +33 -0
- data/app/assets/javascripts/garage/application.js +16 -0
- data/app/assets/javascripts/garage/docs/console.js.coffee +90 -0
- data/app/assets/javascripts/garage/docs/jquery.colorbox.js +1026 -0
- data/app/assets/stylesheets/garage/application.css +14 -0
- data/app/assets/stylesheets/garage/colorbox.scss +62 -0
- data/app/assets/stylesheets/garage/style.scss +59 -0
- data/app/assets/stylesheets/vendor/bootstrap.min.css +9 -0
- data/app/controllers/garage/application_controller.rb +4 -0
- data/app/controllers/garage/docs/resources_controller.rb +103 -0
- data/app/controllers/garage/meta/docs_controller.rb +20 -0
- data/app/controllers/garage/meta/services_controller.rb +20 -0
- data/app/helpers/garage/application_helper.rb +4 -0
- data/app/helpers/garage/docs/resources_helper.rb +24 -0
- data/app/models/garage/hash_representer.rb +11 -0
- data/app/views/garage/docs/resources/_layout_navigation.html.haml +5 -0
- data/app/views/garage/docs/resources/_navigation.html.haml +6 -0
- data/app/views/garage/docs/resources/callback.html.haml +5 -0
- data/app/views/garage/docs/resources/console.html.haml +45 -0
- data/app/views/garage/docs/resources/index.html.haml +2 -0
- data/app/views/garage/docs/resources/show.html.haml +16 -0
- data/app/views/layouts/garage/application.html.haml +26 -0
- data/config/routes.rb +0 -0
- data/lib/garage/app_responder.rb +22 -0
- data/lib/garage/authorizable.rb +26 -0
- data/lib/garage/config.rb +76 -0
- data/lib/garage/controller_helper.rb +110 -0
- data/lib/garage/docs/anchor_building.rb +28 -0
- data/lib/garage/docs/application.rb +24 -0
- data/lib/garage/docs/config.rb +61 -0
- data/lib/garage/docs/console_link_building.rb +14 -0
- data/lib/garage/docs/document.rb +141 -0
- data/lib/garage/docs/engine.rb +35 -0
- data/lib/garage/docs/example.rb +26 -0
- data/lib/garage/docs/renderer.rb +17 -0
- data/lib/garage/docs/toc_renderer.rb +14 -0
- data/lib/garage/docs.rb +9 -0
- data/lib/garage/exceptions.rb +49 -0
- data/lib/garage/hypermedia_filter.rb +44 -0
- data/lib/garage/hypermedia_responder.rb +120 -0
- data/lib/garage/meta/engine.rb +16 -0
- data/lib/garage/meta/remote_service.rb +78 -0
- data/lib/garage/meta.rb +6 -0
- data/lib/garage/meta_resource.rb +17 -0
- data/lib/garage/nested_field_query.rb +183 -0
- data/lib/garage/optional_response_body_responder.rb +16 -0
- data/lib/garage/paginating_responder.rb +113 -0
- data/lib/garage/permission.rb +13 -0
- data/lib/garage/permissions.rb +75 -0
- data/lib/garage/representer.rb +214 -0
- data/lib/garage/resource_casting_responder.rb +13 -0
- data/lib/garage/restful_actions.rb +219 -0
- data/lib/garage/strategy/access_token.rb +57 -0
- data/lib/garage/strategy/auth_server.rb +200 -0
- data/lib/garage/strategy/no_authentication.rb +13 -0
- data/lib/garage/strategy/test.rb +44 -0
- data/lib/garage/strategy.rb +4 -0
- data/lib/garage/test/migrator.rb +31 -0
- data/lib/garage/token_scope.rb +134 -0
- data/lib/garage/utils.rb +28 -0
- data/lib/garage/version.rb +3 -0
- data/lib/garage.rb +23 -0
- metadata +275 -0
data/config/routes.rb
ADDED
File without changes
|
@@ -0,0 +1,22 @@
|
|
1
|
+
if Rails::VERSION::MAJOR > 4 ||
|
2
|
+
(Rails::VERSION::MAJOR == 4 && Rails::VERSION::MINOR >= 2)
|
3
|
+
require 'action_controller/responder'
|
4
|
+
else
|
5
|
+
require 'action_controller'
|
6
|
+
end
|
7
|
+
|
8
|
+
require "garage/hypermedia_responder"
|
9
|
+
require "garage/resource_casting_responder"
|
10
|
+
require "garage/paginating_responder"
|
11
|
+
require "garage/optional_response_body_responder"
|
12
|
+
|
13
|
+
class Garage::AppResponder < ActionController::Responder
|
14
|
+
# like Rack middleware, responders are applied outside in, bottom to the top
|
15
|
+
include Garage::HypermediaResponder
|
16
|
+
include Garage::ResourceCastingResponder
|
17
|
+
include Garage::PaginatingResponder
|
18
|
+
include Garage::OptionalResponseBodyResponder
|
19
|
+
|
20
|
+
# in case someone tries to do Object#to_msgpack
|
21
|
+
undef_method(:to_msgpack) if method_defined?(:to_msgpack)
|
22
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Public: include this module to make the resource authorizable in Garage::RestfulActions
|
2
|
+
#
|
3
|
+
# Examples
|
4
|
+
#
|
5
|
+
# class Post
|
6
|
+
# include Garage::Authorizable
|
7
|
+
# def build_permissions(perms, other); end
|
8
|
+
# def self.build_permissions(perms, other, target); end
|
9
|
+
# end
|
10
|
+
module Garage
|
11
|
+
module Authorizable
|
12
|
+
def build_permissions(perms, subject)
|
13
|
+
raise NotImplementedError, "#{self.class}#build_permissions must be implemented"
|
14
|
+
end
|
15
|
+
|
16
|
+
def effective_permissions(subject)
|
17
|
+
Garage::Permissions.new(subject, resource_class).tap do |perms|
|
18
|
+
build_permissions(perms, subject)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def authorize!(subject, action)
|
23
|
+
effective_permissions(subject).authorize!(action)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'garage/docs/config'
|
2
|
+
|
3
|
+
module Garage
|
4
|
+
def self.configure(&block)
|
5
|
+
@config = Config::Builder.new(&block).build
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.configuration
|
9
|
+
@config
|
10
|
+
end
|
11
|
+
|
12
|
+
class Config
|
13
|
+
DEFAULT_RESCUE_ERROR = true
|
14
|
+
|
15
|
+
attr_writer :cast_resource, :docs, :rescue_error, :strategy, :cache_acceess_token_validation
|
16
|
+
attr_accessor :auth_server_url, :auth_server_host, :auth_server_timeout, :ttl_for_access_token_cache
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
@cache_acceess_token_validation = false
|
20
|
+
@ttl_for_access_token_cache = 5.minutes
|
21
|
+
end
|
22
|
+
|
23
|
+
# Set false if you want to rescue errors by yourself
|
24
|
+
# @return [true, false] A flag to rescue Garage::HTTPError in ControllerHelper (default: true)
|
25
|
+
# @example
|
26
|
+
# Garage.configuration.rescue_error = false
|
27
|
+
def rescue_error
|
28
|
+
instance_variable_defined?(:@rescue_error) ? @rescue_error : DEFAULT_RESCUE_ERROR
|
29
|
+
end
|
30
|
+
|
31
|
+
# Set authentication strategy module which must satisfy Strategy interface.
|
32
|
+
# @return [Module] A auth strategy. default is NoAuthentication strategy.
|
33
|
+
# @example
|
34
|
+
# Garage.configuration.strategy = Garage::Strategy::AuthServer
|
35
|
+
def strategy
|
36
|
+
instance_variable_defined?(:@strategy) ? @strategy : Garage::Strategy::NoAuthentication
|
37
|
+
end
|
38
|
+
|
39
|
+
def docs
|
40
|
+
@docs ||= Docs::Config.new
|
41
|
+
end
|
42
|
+
|
43
|
+
def cast_resource
|
44
|
+
@cast_resource ||= proc { |resource|
|
45
|
+
if resource.respond_to?(:map) && resource.respond_to?(:to_a)
|
46
|
+
resource.map(&:to_resource)
|
47
|
+
else
|
48
|
+
resource.to_resource
|
49
|
+
end
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
def cache_acceess_token_validation?
|
54
|
+
!!@cache_acceess_token_validation
|
55
|
+
end
|
56
|
+
|
57
|
+
class Builder
|
58
|
+
def initialize(&block)
|
59
|
+
@config = Config.new
|
60
|
+
instance_eval(&block)
|
61
|
+
end
|
62
|
+
|
63
|
+
def build
|
64
|
+
@config
|
65
|
+
end
|
66
|
+
|
67
|
+
def cast_resource(&block)
|
68
|
+
@config.cast_resource = block
|
69
|
+
end
|
70
|
+
|
71
|
+
def docs
|
72
|
+
@docs_builder ||= Docs::Config::Builder.new(@config.docs)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Garage
|
2
|
+
module ControllerHelper
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include Utils
|
5
|
+
|
6
|
+
included do
|
7
|
+
use Rack::AcceptDefault
|
8
|
+
|
9
|
+
around_action :notify_request_stats
|
10
|
+
|
11
|
+
include Garage.configuration.strategy
|
12
|
+
|
13
|
+
if Garage.configuration.rescue_error
|
14
|
+
rescue_from Garage::HTTPError do |exception|
|
15
|
+
render json: { status_code: exception.status_code, error: exception.message }, status: exception.status
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
before_action Garage::HypermediaFilter
|
20
|
+
|
21
|
+
respond_to :json # , :msgpack
|
22
|
+
self.responder = Garage::AppResponder
|
23
|
+
end
|
24
|
+
|
25
|
+
# For backword compatiblility.
|
26
|
+
def doorkeeper_token
|
27
|
+
access_token
|
28
|
+
end
|
29
|
+
|
30
|
+
def resource_owner_id
|
31
|
+
access_token.try(:resource_owner_id)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Use this method to render 'unauthorized'.
|
35
|
+
# Garage user may overwrite this method to response custom unauthorized response.
|
36
|
+
# @return [Hash]
|
37
|
+
def unauthorized_render_options(error: nil)
|
38
|
+
{ json: { status_code: 401, error: "Unauthorized (invalid token)" } }
|
39
|
+
end
|
40
|
+
|
41
|
+
# Implement by using `resource_owner_id` like:
|
42
|
+
#
|
43
|
+
# def current_resource_owner
|
44
|
+
# @current_resource_owner ||= User.find(resource_owner_id) if resource_owner_id
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
def current_resource_owner
|
48
|
+
raise "Your ApplicationController needs to implement current_resource_owner!"
|
49
|
+
end
|
50
|
+
|
51
|
+
# Check if the current resource is the same as the requester.
|
52
|
+
# The resource must respond to `resource.id` method.
|
53
|
+
def requested_by?(resource)
|
54
|
+
user = resource.respond_to?(:owner) ? resource.owner : resource
|
55
|
+
case
|
56
|
+
when current_resource_owner.nil?
|
57
|
+
false
|
58
|
+
when !user.is_a?(current_resource_owner.class)
|
59
|
+
false
|
60
|
+
when current_resource_owner.id == user.id
|
61
|
+
true
|
62
|
+
else
|
63
|
+
false
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Public: returns if the current request includes the given OAuth scope
|
68
|
+
def has_scope?(scope)
|
69
|
+
access_token && access_token.scopes.include?(scope)
|
70
|
+
end
|
71
|
+
|
72
|
+
def cache_context
|
73
|
+
{ t: access_token.try(:id) }
|
74
|
+
end
|
75
|
+
|
76
|
+
attr_accessor :representation, :field_selector
|
77
|
+
|
78
|
+
def allow_access?(klass, action = :read)
|
79
|
+
ability_from_token.allow?(klass, action)
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def ability_from_token
|
85
|
+
Garage::TokenScope.ability(current_resource_owner, access_token.try(:scopes) || [])
|
86
|
+
end
|
87
|
+
|
88
|
+
def notify_request_stats
|
89
|
+
yield
|
90
|
+
ensure
|
91
|
+
begin
|
92
|
+
payload = {
|
93
|
+
:controller => self,
|
94
|
+
:token => access_token,
|
95
|
+
:resource_owner => current_resource_owner,
|
96
|
+
}
|
97
|
+
ActiveSupport::Notifications.instrument("garage.request", payload)
|
98
|
+
rescue Exception
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def verify_auth
|
103
|
+
if !access_token || !access_token.accessible?
|
104
|
+
error_status = :unauthorized
|
105
|
+
options = unauthorized_render_options
|
106
|
+
render options.merge(status: error_status)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Garage
|
2
|
+
module Docs
|
3
|
+
module AnchorBuilding
|
4
|
+
def preprocess(full_document)
|
5
|
+
reset
|
6
|
+
full_document
|
7
|
+
end
|
8
|
+
|
9
|
+
def postprocess(full_document)
|
10
|
+
reset
|
11
|
+
full_document
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def reset
|
17
|
+
@anchors = Hash.new(0)
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_anchor(text)
|
21
|
+
unique_text = text + @anchors[text].to_s
|
22
|
+
@anchors[text] += 1
|
23
|
+
|
24
|
+
unique_text.gsub(/\s+/, '-').gsub(/<\/?[^>]*>/, '').downcase
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Garage
|
2
|
+
module Docs
|
3
|
+
class Application
|
4
|
+
def initialize(application)
|
5
|
+
@application = application
|
6
|
+
end
|
7
|
+
|
8
|
+
def name
|
9
|
+
@application.class.name.split("::")[0]
|
10
|
+
end
|
11
|
+
|
12
|
+
def documents
|
13
|
+
cached = Garage.configuration.docs.docs_cache_enabled
|
14
|
+
@documents ||= pathnames.map {|pathname| Garage::Docs::Document.new(pathname, cached) }
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def pathnames
|
20
|
+
Pathname.glob("#{Garage.configuration.docs.document_root}/resources/**/*.md").sort
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Garage
|
2
|
+
module Docs
|
3
|
+
class Config
|
4
|
+
attr_accessor :document_root, :current_user_method, :authenticate,
|
5
|
+
:console_app_uid, :console_app_secret, :remote_server,
|
6
|
+
:docs_authorization_method, :docs_cache_enabled
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
reset
|
10
|
+
end
|
11
|
+
|
12
|
+
def reset
|
13
|
+
@document_root = Rails.root.join('doc/garage')
|
14
|
+
@current_user_method = Proc.new { current_user }
|
15
|
+
@authenticate = Proc.new {}
|
16
|
+
@console_app_uid = ''
|
17
|
+
@remote_server = Proc.new {|request| "#{request.protocol}#{request.host_with_port}" }
|
18
|
+
@docs_authorization_method = nil
|
19
|
+
@docs_cache_enabled = true
|
20
|
+
end
|
21
|
+
|
22
|
+
class Builder
|
23
|
+
def initialize(config)
|
24
|
+
@config = config
|
25
|
+
end
|
26
|
+
|
27
|
+
def document_root=(value)
|
28
|
+
@config.document_root = value
|
29
|
+
end
|
30
|
+
|
31
|
+
def current_user_method(&block)
|
32
|
+
@config.current_user_method = block
|
33
|
+
end
|
34
|
+
|
35
|
+
def authenticate(&block)
|
36
|
+
@config.authenticate = block
|
37
|
+
end
|
38
|
+
|
39
|
+
def console_app_uid=(value)
|
40
|
+
@config.console_app_uid = value
|
41
|
+
end
|
42
|
+
|
43
|
+
def console_app_secret=(value)
|
44
|
+
@config.console_app_secret = value
|
45
|
+
end
|
46
|
+
|
47
|
+
def remote_server=(value)
|
48
|
+
@config.remote_server = value
|
49
|
+
end
|
50
|
+
|
51
|
+
def docs_cache_enabled=(value)
|
52
|
+
@config.docs_cache_enabled = value
|
53
|
+
end
|
54
|
+
|
55
|
+
def docs_authorization_method(&block)
|
56
|
+
@config.docs_authorization_method = block
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Garage
|
2
|
+
module Docs
|
3
|
+
module ConsoleLinkBuilding
|
4
|
+
def build_console_link(text)
|
5
|
+
if text.match(/^(POST|GET|PUT|DELETE)\s+(\/.*)$/)
|
6
|
+
query = Rack::Utils.build_query(method: $1, location: $2)
|
7
|
+
"<a href='console?#{query}'><small>(console)</small></a>"
|
8
|
+
else
|
9
|
+
''
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
module Garage
|
2
|
+
module Docs
|
3
|
+
class Document
|
4
|
+
class << self
|
5
|
+
def all
|
6
|
+
application.documents
|
7
|
+
end
|
8
|
+
|
9
|
+
def find_by_name(name)
|
10
|
+
all.find {|document| document.name == name }
|
11
|
+
end
|
12
|
+
|
13
|
+
def renderer
|
14
|
+
@renderer ||= Redcarpet::Markdown.new(
|
15
|
+
Garage::Docs::Renderer.new(with_toc_data: true),
|
16
|
+
fenced_code_blocks: true,
|
17
|
+
no_intra_emphasis: true,
|
18
|
+
tables: true,
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def toc_renderer
|
23
|
+
@toc_renderer ||= Redcarpet::Markdown.new(
|
24
|
+
Garage::Docs::TocRenderer.new,
|
25
|
+
no_intra_emphasis: true
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
def build_permissions(perms, other, target)
|
30
|
+
perms.permits! :read
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def application
|
36
|
+
Garage::Docs.application
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
include Garage::Authorizable
|
41
|
+
include Garage::Representer
|
42
|
+
|
43
|
+
property :name
|
44
|
+
property :toc
|
45
|
+
property :rendered_body
|
46
|
+
|
47
|
+
attr_reader :pathname
|
48
|
+
attr_accessor :cached
|
49
|
+
|
50
|
+
def initialize(pathname, cached = false)
|
51
|
+
@pathname = pathname
|
52
|
+
@cached = cached
|
53
|
+
end
|
54
|
+
|
55
|
+
def name
|
56
|
+
relative_base_name.to_s.gsub('/', '-')
|
57
|
+
end
|
58
|
+
|
59
|
+
def humanized_name
|
60
|
+
name.split('-').map(&:humanize).join(' / ')
|
61
|
+
end
|
62
|
+
|
63
|
+
def cache_key(type)
|
64
|
+
"garage-doc-#{type}-#{pathname}"
|
65
|
+
end
|
66
|
+
|
67
|
+
def toc
|
68
|
+
if cached
|
69
|
+
Rails.cache.fetch(cache_key(:toc)) do
|
70
|
+
self.class.toc_renderer.render(body).html_safe
|
71
|
+
end
|
72
|
+
else
|
73
|
+
self.class.toc_renderer.render(body).html_safe
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def render
|
78
|
+
if cached
|
79
|
+
Rails.cache.fetch(cache_key(:render)) do
|
80
|
+
self.class.renderer.render(body).html_safe
|
81
|
+
end
|
82
|
+
else
|
83
|
+
self.class.renderer.render(body).html_safe
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
alias :rendered_body :render
|
88
|
+
|
89
|
+
def body
|
90
|
+
pathname.read
|
91
|
+
end
|
92
|
+
|
93
|
+
def resource_class
|
94
|
+
@resource_class ||= extract_resource_class || relative_base_name.camelize.singularize.constantize
|
95
|
+
rescue NameError
|
96
|
+
nil
|
97
|
+
end
|
98
|
+
|
99
|
+
def examples(*args)
|
100
|
+
if resource_class && resource_class.respond_to?(:garage_examples)
|
101
|
+
resource_class.garage_examples(*args)
|
102
|
+
else
|
103
|
+
[]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# If you need authentication logic,
|
108
|
+
# assign a Proc to Garage.docs.configuration.docs_authorization_method.
|
109
|
+
#
|
110
|
+
# Example:
|
111
|
+
#
|
112
|
+
# Garage.docs.configuration.docs_authorization_method do |args|
|
113
|
+
# if name.start_with?("admin_")
|
114
|
+
# args[:user].admin?
|
115
|
+
# else
|
116
|
+
# true
|
117
|
+
# end
|
118
|
+
# end
|
119
|
+
#
|
120
|
+
def visible_to?(user)
|
121
|
+
if method = Garage.configuration.docs.docs_authorization_method
|
122
|
+
method.call(document: self, user: user)
|
123
|
+
else
|
124
|
+
true
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
def extract_resource_class
|
131
|
+
if /<!-- resource_class: (\S+)/ === body
|
132
|
+
$1.constantize
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def relative_base_name
|
137
|
+
pathname.relative_path_from(Pathname.new("#{Garage.configuration.docs.document_root}/resources")).to_s.sub('.md', '')
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|