the_garage 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +162 -0
  4. data/Rakefile +33 -0
  5. data/app/assets/javascripts/garage/application.js +16 -0
  6. data/app/assets/javascripts/garage/docs/console.js.coffee +90 -0
  7. data/app/assets/javascripts/garage/docs/jquery.colorbox.js +1026 -0
  8. data/app/assets/stylesheets/garage/application.css +14 -0
  9. data/app/assets/stylesheets/garage/colorbox.scss +62 -0
  10. data/app/assets/stylesheets/garage/style.scss +59 -0
  11. data/app/assets/stylesheets/vendor/bootstrap.min.css +9 -0
  12. data/app/controllers/garage/application_controller.rb +4 -0
  13. data/app/controllers/garage/docs/resources_controller.rb +103 -0
  14. data/app/controllers/garage/meta/docs_controller.rb +20 -0
  15. data/app/controllers/garage/meta/services_controller.rb +20 -0
  16. data/app/helpers/garage/application_helper.rb +4 -0
  17. data/app/helpers/garage/docs/resources_helper.rb +24 -0
  18. data/app/models/garage/hash_representer.rb +11 -0
  19. data/app/views/garage/docs/resources/_layout_navigation.html.haml +5 -0
  20. data/app/views/garage/docs/resources/_navigation.html.haml +6 -0
  21. data/app/views/garage/docs/resources/callback.html.haml +5 -0
  22. data/app/views/garage/docs/resources/console.html.haml +45 -0
  23. data/app/views/garage/docs/resources/index.html.haml +2 -0
  24. data/app/views/garage/docs/resources/show.html.haml +16 -0
  25. data/app/views/layouts/garage/application.html.haml +26 -0
  26. data/config/routes.rb +0 -0
  27. data/lib/garage/app_responder.rb +22 -0
  28. data/lib/garage/authorizable.rb +26 -0
  29. data/lib/garage/config.rb +76 -0
  30. data/lib/garage/controller_helper.rb +110 -0
  31. data/lib/garage/docs/anchor_building.rb +28 -0
  32. data/lib/garage/docs/application.rb +24 -0
  33. data/lib/garage/docs/config.rb +61 -0
  34. data/lib/garage/docs/console_link_building.rb +14 -0
  35. data/lib/garage/docs/document.rb +141 -0
  36. data/lib/garage/docs/engine.rb +35 -0
  37. data/lib/garage/docs/example.rb +26 -0
  38. data/lib/garage/docs/renderer.rb +17 -0
  39. data/lib/garage/docs/toc_renderer.rb +14 -0
  40. data/lib/garage/docs.rb +9 -0
  41. data/lib/garage/exceptions.rb +49 -0
  42. data/lib/garage/hypermedia_filter.rb +44 -0
  43. data/lib/garage/hypermedia_responder.rb +120 -0
  44. data/lib/garage/meta/engine.rb +16 -0
  45. data/lib/garage/meta/remote_service.rb +78 -0
  46. data/lib/garage/meta.rb +6 -0
  47. data/lib/garage/meta_resource.rb +17 -0
  48. data/lib/garage/nested_field_query.rb +183 -0
  49. data/lib/garage/optional_response_body_responder.rb +16 -0
  50. data/lib/garage/paginating_responder.rb +113 -0
  51. data/lib/garage/permission.rb +13 -0
  52. data/lib/garage/permissions.rb +75 -0
  53. data/lib/garage/representer.rb +214 -0
  54. data/lib/garage/resource_casting_responder.rb +13 -0
  55. data/lib/garage/restful_actions.rb +219 -0
  56. data/lib/garage/strategy/access_token.rb +57 -0
  57. data/lib/garage/strategy/auth_server.rb +200 -0
  58. data/lib/garage/strategy/no_authentication.rb +13 -0
  59. data/lib/garage/strategy/test.rb +44 -0
  60. data/lib/garage/strategy.rb +4 -0
  61. data/lib/garage/test/migrator.rb +31 -0
  62. data/lib/garage/token_scope.rb +134 -0
  63. data/lib/garage/utils.rb +28 -0
  64. data/lib/garage/version.rb +3 -0
  65. data/lib/garage.rb +23 -0
  66. 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