the_garage 2.0.0
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.
- 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
|