shaf 1.6.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (181) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/iana_link_relations.csv.gz +0 -0
  4. data/lib/shaf/alps/attribute_serializer.rb +41 -0
  5. data/lib/shaf/alps/json_serializer.rb +50 -0
  6. data/lib/shaf/alps/relation_serializer.rb +70 -0
  7. data/lib/shaf/app.rb +32 -7
  8. data/lib/shaf/authenticator/base.rb +161 -0
  9. data/lib/shaf/authenticator/basic_auth.rb +25 -0
  10. data/lib/shaf/authenticator/challenge.rb +32 -0
  11. data/lib/shaf/authenticator/parameter.rb +31 -0
  12. data/lib/shaf/authenticator/request.rb +17 -0
  13. data/lib/shaf/authenticator.rb +56 -0
  14. data/lib/shaf/command/base.rb +4 -0
  15. data/lib/shaf/command/console.rb +1 -1
  16. data/lib/shaf/command/generate.rb +5 -2
  17. data/lib/shaf/command/new.rb +24 -7
  18. data/lib/shaf/command/server.rb +5 -1
  19. data/lib/shaf/command/templates/Gemfile.erb +1 -0
  20. data/{templates/config/settings.yml → lib/shaf/command/templates/config/settings.yml.erb} +9 -12
  21. data/lib/shaf/errors.rb +11 -0
  22. data/lib/shaf/extensions/api_routes.rb +60 -0
  23. data/lib/shaf/extensions/authorize.rb +11 -9
  24. data/lib/shaf/extensions/log.rb +1 -1
  25. data/lib/shaf/extensions/resource_uris.rb +95 -137
  26. data/lib/shaf/extensions/symbolic_routes.rb +9 -19
  27. data/lib/shaf/extensions.rb +3 -3
  28. data/lib/shaf/formable/builder.rb +58 -19
  29. data/lib/shaf/formable/form.rb +14 -11
  30. data/lib/shaf/formable.rb +10 -24
  31. data/lib/shaf/generator/base.rb +84 -3
  32. data/lib/shaf/generator/controller.rb +30 -42
  33. data/lib/shaf/generator/doc.rb +17 -0
  34. data/lib/shaf/generator/forms.rb +11 -14
  35. data/lib/shaf/generator/helper.rb +2 -1
  36. data/lib/shaf/generator/migration/add_column.rb +0 -4
  37. data/lib/shaf/generator/migration/add_index.rb +0 -4
  38. data/lib/shaf/generator/migration/base.rb +15 -3
  39. data/lib/shaf/generator/migration/create_table.rb +0 -4
  40. data/lib/shaf/generator/migration/drop_column.rb +0 -4
  41. data/lib/shaf/generator/migration/rename_column.rb +0 -4
  42. data/lib/shaf/generator/migration/type.rb +4 -26
  43. data/lib/shaf/generator/migration/types.rb +45 -16
  44. data/lib/shaf/generator/model.rb +29 -15
  45. data/lib/shaf/generator/policy.rb +8 -14
  46. data/lib/shaf/generator/profile.rb +47 -0
  47. data/lib/shaf/generator/scaffold.rb +6 -9
  48. data/lib/shaf/generator/serializer.rb +64 -98
  49. data/lib/shaf/generator/templates/api/controller.rb.erb +13 -13
  50. data/lib/shaf/generator/templates/api/forms.rb.erb +2 -2
  51. data/lib/shaf/generator/templates/api/model.rb.erb +1 -1
  52. data/lib/shaf/generator/templates/api/policy.rb.erb +2 -2
  53. data/lib/shaf/generator/templates/api/profile.rb.erb +16 -0
  54. data/lib/shaf/generator/templates/api/serializer.rb.erb +3 -3
  55. data/lib/shaf/generator/templates/spec/integration_spec.rb.erb +15 -16
  56. data/lib/shaf/generator/templates/spec/serializer_spec.rb.erb +5 -5
  57. data/lib/shaf/generator.rb +2 -0
  58. data/lib/shaf/helpers/authentication.rb +79 -0
  59. data/lib/shaf/helpers/paginate.rb +1 -1
  60. data/lib/shaf/helpers/payload.rb +14 -34
  61. data/lib/shaf/helpers/vary.rb +8 -0
  62. data/lib/shaf/helpers.rb +4 -0
  63. data/lib/shaf/logger.rb +12 -0
  64. data/lib/shaf/parser/base.rb +44 -0
  65. data/lib/shaf/parser/form_data.rb +15 -0
  66. data/lib/shaf/parser/json.rb +26 -0
  67. data/lib/shaf/parser.rb +65 -0
  68. data/lib/shaf/profile/attribute.rb +29 -0
  69. data/lib/shaf/profile/evaluator.rb +46 -0
  70. data/lib/shaf/profile/relation.rb +29 -0
  71. data/lib/shaf/profile/unique_id.rb +58 -0
  72. data/lib/shaf/profile.rb +115 -0
  73. data/lib/shaf/profiles/shaf_basic.rb +20 -0
  74. data/lib/shaf/profiles/shaf_error.rb +49 -0
  75. data/lib/shaf/profiles/shaf_form.rb +110 -0
  76. data/lib/shaf/profiles.rb +46 -0
  77. data/lib/shaf/registrable_factory.rb +62 -32
  78. data/lib/shaf/responder/alps_json.rb +25 -0
  79. data/lib/shaf/responder/base.rb +20 -17
  80. data/lib/shaf/responder/hal.rb +66 -5
  81. data/lib/shaf/responder/html.rb +43 -16
  82. data/lib/shaf/responder/problem_json.rb +2 -2
  83. data/lib/shaf/responder.rb +41 -2
  84. data/lib/shaf/router.rb +65 -12
  85. data/lib/shaf/serializer.rb +35 -0
  86. data/lib/shaf/settings.rb +25 -12
  87. data/lib/shaf/spec/authenticator.rb +13 -0
  88. data/lib/shaf/spec/base.rb +1 -1
  89. data/lib/shaf/spec/http_method_utils.rb +1 -1
  90. data/lib/shaf/spec/integration_spec.rb +26 -14
  91. data/lib/shaf/spec/payload_utils.rb +2 -2
  92. data/lib/shaf/spec.rb +1 -0
  93. data/lib/shaf/supported_http_methods.rb +15 -0
  94. data/lib/shaf/tasks/routes_task.rb +14 -17
  95. data/lib/shaf/tasks.rb +0 -1
  96. data/lib/shaf/upgrade/manifest.rb +11 -2
  97. data/lib/shaf/upgrade/package.rb +73 -45
  98. data/lib/shaf/upgrade/version.rb +11 -10
  99. data/lib/shaf/utils.rb +19 -5
  100. data/lib/shaf/version.rb +3 -1
  101. data/lib/shaf/yard/attribute_method_handler.rb +19 -0
  102. data/lib/shaf/yard/attribute_object.rb +30 -0
  103. data/lib/shaf/yard/base_method_handler.rb +30 -0
  104. data/lib/shaf/yard/link_method_handler.rb +39 -0
  105. data/lib/shaf/yard/link_object.rb +60 -0
  106. data/lib/shaf/yard/nested_attributes.rb +37 -0
  107. data/lib/shaf/yard/parser.rb +64 -0
  108. data/lib/shaf/yard/profile_method_handler.rb +51 -0
  109. data/lib/shaf/yard/profile_object.rb +21 -0
  110. data/lib/shaf/yard/resource_object.rb +55 -0
  111. data/lib/shaf/yard/serializer_handler.rb +27 -0
  112. data/lib/shaf/yard.rb +34 -0
  113. data/lib/shaf.rb +6 -0
  114. data/templates/Rakefile +0 -6
  115. data/templates/api/controllers/base_controller.rb +0 -12
  116. data/templates/api/controllers/docs_controller.rb +5 -3
  117. data/templates/api/controllers/root_controller.rb +7 -1
  118. data/templates/api/policies/base_policy.rb +2 -0
  119. data/templates/api/serializers/base_serializer.rb +1 -3
  120. data/templates/api/serializers/error_serializer.rb +1 -5
  121. data/templates/api/serializers/form_serializer.rb +1 -5
  122. data/templates/api/serializers/root_serializer.rb +0 -11
  123. data/templates/api/serializers/validation_error_serializer.rb +1 -5
  124. data/templates/config/bootstrap.rb +1 -2
  125. data/templates/config/directories.rb +52 -44
  126. data/templates/config/helpers.rb +1 -1
  127. data/templates/config/initializers/authentication.rb +18 -0
  128. data/templates/config/initializers/db_migrations.rb +2 -2
  129. data/templates/config/initializers/logging.rb +2 -2
  130. data/templates/config/initializers/middleware.rb +6 -0
  131. data/templates/config/initializers.rb +52 -8
  132. data/templates/config.ru +1 -1
  133. data/templates/spec/spec_helper.rb +5 -0
  134. data/upgrades/0.5.0.tar.gz +0 -0
  135. data/upgrades/1.0.4.tar.gz +0 -0
  136. data/upgrades/1.1.0.tar.gz +0 -0
  137. data/upgrades/1.6.1.tar.gz +0 -0
  138. data/upgrades/2.0.0.tar.gz +0 -0
  139. data/upgrades/3.0.0.tar.gz +0 -0
  140. data/yard_templates/api_doc/doc_index/html/body.erb +3 -0
  141. data/yard_templates/api_doc/doc_index/setup.rb +8 -0
  142. data/yard_templates/api_doc/html/css/api-doc.css +222 -0
  143. data/yard_templates/api_doc/html/favicon.ico +0 -0
  144. data/yard_templates/api_doc/html/js/switch_tab.js +17 -0
  145. data/yard_templates/api_doc/html/setup.rb +59 -0
  146. data/yard_templates/api_doc/layout/html/footer.erb +3 -0
  147. data/yard_templates/api_doc/layout/html/header.erb +7 -0
  148. data/yard_templates/api_doc/layout/html/layout.erb +13 -0
  149. data/yard_templates/api_doc/layout/setup.rb +24 -0
  150. data/yard_templates/api_doc/profile/html/attributes.erb +10 -0
  151. data/yard_templates/api_doc/profile/html/profile.erb +6 -0
  152. data/yard_templates/api_doc/profile/html/relations.erb +10 -0
  153. data/yard_templates/api_doc/profile/setup.rb +38 -0
  154. data/yard_templates/api_doc/profile_attribute/html/attribute.erb +23 -0
  155. data/yard_templates/api_doc/profile_attribute/setup.rb +21 -0
  156. data/yard_templates/api_doc/profile_relation/html/relation.erb +37 -0
  157. data/yard_templates/api_doc/profile_relation/setup.rb +41 -0
  158. data/yard_templates/api_doc/resource/html/attributes.erb +10 -0
  159. data/yard_templates/api_doc/resource/html/profile.erb +14 -0
  160. data/yard_templates/api_doc/resource/html/relations.erb +10 -0
  161. data/yard_templates/api_doc/resource/html/resource.erb +5 -0
  162. data/yard_templates/api_doc/resource/setup.rb +56 -0
  163. data/yard_templates/api_doc/resource_attribute/html/attribute.erb +23 -0
  164. data/yard_templates/api_doc/resource_attribute/setup.rb +20 -0
  165. data/yard_templates/api_doc/resource_relation/html/relation.erb +47 -0
  166. data/yard_templates/api_doc/resource_relation/setup.rb +80 -0
  167. data/yard_templates/api_doc/setup.rb +31 -0
  168. data/yard_templates/api_doc/sidebar/html/profile_list.erb +8 -0
  169. data/yard_templates/api_doc/sidebar/html/search.erb +7 -0
  170. data/yard_templates/api_doc/sidebar/html/serializer_list.erb +8 -0
  171. data/yard_templates/api_doc/sidebar/html/sidebar.erb +13 -0
  172. data/yard_templates/api_doc/sidebar/setup.rb +56 -0
  173. data.tar.gz.sig +2 -2
  174. metadata +143 -38
  175. metadata.gz.sig +0 -0
  176. data/lib/shaf/api_doc/comment.rb +0 -27
  177. data/lib/shaf/api_doc/document.rb +0 -137
  178. data/lib/shaf/extensions/current_user.rb +0 -48
  179. data/lib/shaf/middleware.rb +0 -1
  180. data/lib/shaf/responder/hal_serializable.rb +0 -64
  181. data/lib/shaf/tasks/api_doc_task.rb +0 -125
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd5e02da6f878d5f9e0ef2e4d3adb641e9ea34362c943cff5a9e94d93ffaad26
4
- data.tar.gz: 565dd3872f4b46a48955b94ebbb6324472ec6293bf1f7ead551a55fdc5bd4d4a
3
+ metadata.gz: aa31b5bc9a9cc80cc5b34c5a3f6101eb17ebf8166bd1a6ae46f0008709c3fa0f
4
+ data.tar.gz: d4fc21aa271c30bb3e52bbc65571917beb0b16029e7ee764fc8705a5d4197ca8
5
5
  SHA512:
6
- metadata.gz: 129bf19d6c7ff6ff0f1df840078fbba5df8e47045e69a5b3a3e40ebdc2680f46ad36dc3c4660f8dd8ba5187368fa3b0ecdb503b0575def9f1403ed0d6e743935
7
- data.tar.gz: c4fa82f52be8f910f40cebe13b0c5df52239a780788d7d083bc6f969cbfb02c9d72a4aa1fa1e2dce6cc23013be92e9d0eaacc849beaf9e05decacc34ccb9ed3d
6
+ metadata.gz: 6820ebc741c7392a44a767d1be674394fb307c3e937997c0428dea3663fd57faa3ab9ff6700dedda76e522650373997413b4a5b256198d9646c4741a91ae33d7
7
+ data.tar.gz: 9315cdd74263a2ae7b39f8a5960267aff7b61c498e0d95ca00559f61a3756f53f84f62b946bb24d8d818d901133cc537ef4402a0b3e201ae34f52b201015f72c
checksums.yaml.gz.sig CHANGED
Binary file
Binary file
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shaf
4
+ module ALPS
5
+ class AttributeSerializer
6
+ attr_reader :attribute
7
+
8
+ def self.call(arg)
9
+ new(arg).to_hash
10
+ end
11
+
12
+ def initialize(attribute)
13
+ @attribute = attribute
14
+ end
15
+
16
+ def to_hash
17
+ {
18
+ id: attribute.id,
19
+ type: 'semantic',
20
+ }.merge(optional_properties)
21
+ end
22
+
23
+ private
24
+
25
+ def optional_properties
26
+ descriptors = serialized_descriptors
27
+ hash = {}
28
+ hash[:href] = attribute.href if attribute.href
29
+ hash[:doc] = { value: attribute.doc } if attribute.doc
30
+ hash[:name] = attribute.name.to_s if attribute.name
31
+ hash[:descriptor] = descriptors unless descriptors.empty?
32
+ hash
33
+ end
34
+
35
+ def serialized_descriptors
36
+ descriptors = attribute.attributes.map { |attr| self.class.call(attr) }
37
+ descriptors += attribute.relations.map { |rel| RelationSerializer.call(rel) }
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shaf/alps/attribute_serializer'
4
+ require 'shaf/alps/relation_serializer'
5
+
6
+ module Shaf
7
+ module ALPS
8
+ class JsonSerializer
9
+ ALPS_VERSION = '1.0'
10
+
11
+ def self.call(profile)
12
+ new(profile).to_hash
13
+ end
14
+
15
+ attr_reader :profile
16
+
17
+ def initialize(profile)
18
+ @profile = profile
19
+ end
20
+
21
+ def to_hash
22
+ {
23
+ alps: {
24
+ version: ALPS_VERSION,
25
+ doc: profile.doc,
26
+ descriptor: descriptors,
27
+ }.compact
28
+ }
29
+ end
30
+
31
+ private
32
+
33
+ def descriptors
34
+ attribute_descriptors + relation_descriptors
35
+ end
36
+
37
+ def attribute_descriptors
38
+ profile.attributes.map do |desc|
39
+ AttributeSerializer.call(desc)
40
+ end
41
+ end
42
+
43
+ def relation_descriptors
44
+ profile.relations.map do |desc|
45
+ RelationSerializer.call(desc)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shaf
4
+ module ALPS
5
+ class RelationSerializer
6
+ SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS']
7
+ IDEMPOTENT_METHODS = ['PUT', 'PATCH', 'DELETE']
8
+ UNSAFE_METHODS = ['POST']
9
+
10
+ attr_reader :rel
11
+
12
+ def self.call(arg)
13
+ new(arg).to_hash
14
+ end
15
+
16
+ def initialize(rel)
17
+ @rel = rel
18
+ end
19
+
20
+ def to_hash
21
+ {
22
+ id: rel.id,
23
+ type: type,
24
+ }.merge(optional_properties)
25
+ end
26
+
27
+ private
28
+
29
+ def optional_properties
30
+ descriptors = serialized_descriptors
31
+ hash = {}
32
+ hash[:href] = rel.href if rel.href
33
+ hash[:doc] = { value: rel.doc } if rel.doc
34
+ hash[:name] = rel.name.to_s if rel.name
35
+ hash[:rt] = rel.content_type if rel.content_type
36
+ hash[:descriptor] = descriptors unless descriptors.empty?
37
+ hash[:ext] = extension if extension
38
+ hash
39
+ end
40
+
41
+ def type
42
+ methods = rel.http_methods
43
+ if methods.all? { |m| SAFE_METHODS.include? m }
44
+ 'safe'
45
+ elsif methods.all? { |m| (SAFE_METHODS + IDEMPOTENT_METHODS).include? m }
46
+ 'idempotent'
47
+ else
48
+ 'unsafe'
49
+ end
50
+ end
51
+
52
+ def extension
53
+ methods = rel.http_methods
54
+ return unless methods
55
+
56
+ [
57
+ {
58
+ id: :http_method,
59
+ href: 'https://gist.github.com/sammyhenningsson/2103d839eb79a7baf8854bfb96bda7ae',
60
+ value: methods,
61
+ }
62
+ ]
63
+ end
64
+
65
+ def serialized_descriptors
66
+ rel.attributes.map { |attr| AttributeSerializer.call(attr) }
67
+ end
68
+ end
69
+ end
70
+ end
data/lib/shaf/app.rb CHANGED
@@ -1,19 +1,44 @@
1
- require 'shaf/middleware'
1
+ require 'sinatra'
2
2
 
3
3
  module Shaf
4
4
  class App
5
5
  class << self
6
+ # Either call `Shaf::App.run!`
6
7
  def run!
7
- app.run!
8
+ instance.run!
8
9
  end
9
10
 
10
- def app
11
- Sinatra.new.tap do |app|
12
- app.set :port, Settings.port || 3000
13
- app.use Middleware::RequestId
14
- app.use Router
11
+ # Or `run Shaf::App` (in config.ru)
12
+ def call(*args)
13
+ instance.call(*args)
14
+ end
15
+
16
+ def instance
17
+ # This works since Sinatra includes Sinatra::Delegator into
18
+ # Rack::Builder, which means that Rack::Builder#set will be delegated
19
+ # to Sinatra::Application
20
+ @instance ||= Rack::Builder.new(app) do
21
+ set :port, Settings.port || 3000
22
+ end
23
+ end
24
+
25
+ def use(middleware, *args, **kwargs, &block)
26
+ if args.empty? && kwargs.empty?
27
+ instance.use middleware, &block
28
+ elsif kwargs.empty?
29
+ instance.use middleware, *args, &block
30
+ elsif args.empty?
31
+ instance.use middleware, **kwargs, &block
32
+ else
33
+ instance.use middleware, *args, **kwargs, &block
15
34
  end
16
35
  end
36
+
37
+ private
38
+
39
+ def app
40
+ Router.new
41
+ end
17
42
  end
18
43
  end
19
44
  end
@@ -0,0 +1,161 @@
1
+ require 'shaf/authenticator/challenge'
2
+ require 'shaf/authenticator/parameter'
3
+ require 'shaf/errors'
4
+
5
+ module Shaf
6
+ module Authenticator
7
+ class Base
8
+ class MissingParametersError < Error
9
+ def initialize(authenticator, *args)
10
+ super("Missing required parameters: [#{args.join(', ')}] for #{authenticator}")
11
+ end
12
+ end
13
+
14
+ class InvalidParameterError < Error
15
+ def initialize(authenticator, *args)
16
+ str = args.map { |key, value| "#{key}: #{value}" }.join(', ')
17
+ super("Invalid parameters for #{authenticator}: #{str}")
18
+ end
19
+ end
20
+
21
+ class WrongCredentialsReturnTypeError < Error
22
+ def initialize(authenticator, clazz)
23
+ super <<~ERR
24
+ #{authenticator}.credentials return an instance of #{clazz}
25
+ It must return an instance of Hash.
26
+ Location: #{authenticator.method(:credentials).source_location})
27
+ ERR
28
+ end
29
+ end
30
+
31
+ class << self
32
+ def inherited(child)
33
+ Authenticator.register(child)
34
+ end
35
+
36
+ def scheme(scheme = nil)
37
+ if scheme
38
+ @scheme = scheme.to_s
39
+ elsif @scheme
40
+ @scheme
41
+ else
42
+ raise Error, "#{self} must specify a scheme!"
43
+ end
44
+ end
45
+
46
+ def scheme?(str)
47
+ return false unless scheme
48
+
49
+ str.to_s.downcase == scheme.downcase
50
+ end
51
+
52
+ def param(name, required: true, default: nil, values: nil)
53
+ params[name.to_sym] = Parameter.new(
54
+ name,
55
+ required: required,
56
+ default: default,
57
+ values: values
58
+ )
59
+ end
60
+
61
+ def restricted(**parameters, &block)
62
+ validate! parameters
63
+ add_defaults! parameters
64
+ challenges << Challenge.new(scheme, **parameters, &block)
65
+ end
66
+
67
+ def challenges_for(realm)
68
+ challenges.select do |challenge|
69
+ challenge.realm? realm
70
+ end
71
+ end
72
+
73
+ def user(request, realm: nil)
74
+ auth = authorization(request)
75
+ cred = credentials(auth, request) || {}
76
+ raise WrongCredentialsReturnTypeError.new(self, cred.class) unless cred.kind_of? Hash
77
+
78
+ return if cred.compact.empty?
79
+
80
+ challenges_for(realm).each do |challenge|
81
+ user = challenge.test(**cred)
82
+ return user if user
83
+ end
84
+
85
+ nil
86
+ end
87
+
88
+ def params
89
+ @params ||= superclass.respond_to?(:params) ? superclass.params.dup : {}
90
+ end
91
+
92
+ protected
93
+
94
+ # Subclasses should implement this method. The return value should be and array
95
+ # that will get passed as block arguments to the block passed to #restricted
96
+ def credentials(authorization, request); end
97
+
98
+ private
99
+
100
+ def challenges
101
+ @challenges ||= []
102
+ end
103
+
104
+ def validate!(parameters)
105
+ validate_required(parameters)
106
+ validate_params(parameters)
107
+ end
108
+
109
+ def validate_required(parameters)
110
+ errors = []
111
+
112
+ required_params.each do |param|
113
+ next if parameters.key? param.name
114
+ next if param.default
115
+ errors << param.name
116
+ end
117
+
118
+ raise MissingParametersError.new(self, *errors) unless errors.empty?
119
+ end
120
+
121
+ def validate_params(parameters)
122
+ errors = []
123
+
124
+ parameters.each do |key, value|
125
+ if params.key? key
126
+ errors << [key, value] unless params[key].valid? value
127
+ else
128
+ logger.warn "Unsupported authenticator parameter " \
129
+ "for #{self}: #{key} = \"#{value}\""
130
+ parameters.delete(key)
131
+ end
132
+ end
133
+
134
+ raise InvalidParameterError.new(self, *errors) unless errors.empty?
135
+ end
136
+
137
+ def logger
138
+ Shaf.logger
139
+ end
140
+
141
+ def add_defaults!(parameters)
142
+ params.each do |key, param|
143
+ next unless param.default
144
+ parameters[key] ||= param.default
145
+ end
146
+ end
147
+
148
+ def required_params
149
+ params.values.select(&:required?)
150
+ end
151
+
152
+ def authorization(request)
153
+ return unless request.authorization
154
+
155
+ request.authorization.sub(/^#{scheme} /i, '')
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
161
+
@@ -0,0 +1,25 @@
1
+ module Shaf
2
+ module Authenticator
3
+ class BasicAuth < Base
4
+ scheme 'Basic'
5
+
6
+ param :realm
7
+ param :charset, required: false, values: ["UTF-8"]
8
+
9
+ def self.credentials(authorization, _request)
10
+ return unless authorization
11
+
12
+ decoded = String(authorization.unpack("m*").first)
13
+ return {} if decoded.empty?
14
+
15
+ user, password = decoded.split(/:/, 2)
16
+ .map { |str| str unless String(str).empty? }
17
+
18
+ {
19
+ user: user,
20
+ password: password
21
+ }
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shaf
4
+ module Authenticator
5
+ class Challenge
6
+ attr_reader :scheme, :parameters, :realm
7
+
8
+ def initialize(scheme, **parameters, &block)
9
+ @scheme = scheme
10
+ @realm = parameters.delete(:realm)&.to_s
11
+ @parameters = parameters
12
+ define_singleton_method(:test, &block)
13
+ end
14
+
15
+ def to_s
16
+ "#{scheme} #{parameter_string}"
17
+ end
18
+
19
+ def realm?(arg)
20
+ realm&.to_s == arg&.to_s
21
+ end
22
+
23
+ private
24
+
25
+ def parameter_string
26
+ params = {}
27
+ params[:realm] = realm if realm
28
+ params.merge(parameters).map { |k,v| %Q(#{k}="#{v}") }.join(', ')
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Shaf
4
+ module Authenticator
5
+ class Parameter
6
+ attr_reader :name, :default, :values
7
+
8
+ def initialize(name, required: true, default: nil, values: nil)
9
+ @name = name
10
+ @required = required
11
+ @default = default
12
+ @values = values&.map(&:downcase)
13
+ end
14
+
15
+ def required?
16
+ @required
17
+ end
18
+
19
+ def optional?
20
+ !required?
21
+ end
22
+
23
+ def valid?(value)
24
+ return optional? if value.nil?
25
+ return true unless values
26
+
27
+ values.include?(value.downcase)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ require 'rack/auth/abstract/request'
2
+
3
+ module Shaf
4
+ module Authenticator
5
+ class Request < Rack::Auth::AbstractRequest
6
+ attr_reader :env
7
+
8
+ def valid?
9
+ !String(authorization).strip.empty?
10
+ end
11
+
12
+ def authorization
13
+ env[authorization_key]
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,56 @@
1
+ require 'set'
2
+ require 'digest'
3
+ require 'shaf/authenticator/request'
4
+
5
+ module Shaf
6
+ module Authenticator
7
+ class << self
8
+ def register(authenticator)
9
+ authenticators << authenticator
10
+ end
11
+
12
+ def unregister(authenticator)
13
+ authenticators.delete_if { |auth| auth == authenticator }
14
+ end
15
+
16
+ def challenges_for(realm: nil)
17
+ authenticators.each_with_object([]) do |authenticator, challenges|
18
+ challenges.concat Array(authenticator.challenges_for(realm))
19
+ end
20
+ end
21
+
22
+ def user(env, realm: nil)
23
+ request = Request.new(env)
24
+ return unless request.provided?
25
+
26
+ authenticator = authenticator_for(request)
27
+ authenticator&.user(request, realm: realm)
28
+ end
29
+
30
+ private
31
+
32
+ def authenticators
33
+ @authenticators ||= Set.new
34
+ end
35
+
36
+ def authenticator_for(request)
37
+ scheme = request.scheme
38
+ authenticator = authenticators.find { |auth| auth.scheme? scheme }
39
+
40
+ logger.warn(
41
+ "Client tried to authenticate with an unsupported " \
42
+ "authentication scheme: #{scheme}"
43
+ ) unless authenticator
44
+
45
+ authenticator
46
+ end
47
+
48
+ def logger
49
+ Shaf.logger
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ require 'shaf/authenticator/base'
56
+ require 'shaf/authenticator/basic_auth'
@@ -23,6 +23,10 @@ module Shaf
23
23
  @usage = str || block
24
24
  end
25
25
 
26
+ def identified_by
27
+ @identifiers
28
+ end
29
+
26
30
  def exit_with_error(msg, status)
27
31
  STDERR.puts msg
28
32
  exit status
@@ -4,7 +4,7 @@ module Shaf
4
4
  module Command
5
5
  class Console < Base
6
6
 
7
- identifier %r(\Ac(onsole)?\Z)
7
+ identifier %r(\Ac(\b|onsole)\Z)
8
8
  usage 'console'
9
9
 
10
10
  def call
@@ -1,10 +1,11 @@
1
+ require 'file_transactions'
1
2
  require 'shaf/generator'
2
3
 
3
4
  module Shaf
4
5
  module Command
5
6
  class Generate < Base
6
7
 
7
- identifier %r(\Ag(en(erate)?)?\Z)
8
+ identifier %r(\Ag(\b|en(\b|erate))\Z)
8
9
  usage Generator::Factory.usage.flatten.sort
9
10
 
10
11
  def self.options(parser, options)
@@ -19,7 +20,9 @@ module Shaf
19
20
 
20
21
  def call
21
22
  in_project_root do
22
- Generator::Factory.create(*args, **options).call
23
+ FileTransactions.transaction do
24
+ Generator::Factory.create(*args, **options).call
25
+ end
23
26
  end
24
27
  rescue StandardError => e
25
28
  raise Command::ArgumentError, e.message
@@ -10,21 +10,26 @@ module Shaf
10
10
  usage 'new PROJECT_NAME'
11
11
 
12
12
  def call
13
- @project_name = args.first
14
- if @project_name.nil? || @project_name.empty?
13
+ self.project_name = args.first
14
+ if project_name.nil? || project_name.empty?
15
15
  raise ArgumentError,
16
16
  "Please provide a project name when using command 'new'!"
17
17
  end
18
18
 
19
- create_dir @project_name
20
- Dir.chdir(@project_name) do
19
+ create_dir project_name
20
+ Dir.chdir(project_name) do
21
21
  copy_templates
22
22
  create_gemfile
23
+ create_settings_file
23
24
  write_shaf_version
24
25
  create_ruby_version_file
25
26
  end
26
27
  end
27
28
 
29
+ private
30
+
31
+ attr_accessor :project_name
32
+
28
33
  def create_dir(name)
29
34
  return if Dir.exist? name
30
35
  FileUtils.mkdir_p(name)
@@ -38,9 +43,21 @@ module Shaf
38
43
  File.write "Gemfile", erb(content)
39
44
  end
40
45
 
41
- def erb(content)
42
- return ERB.new(content, 0, '%-<>').result if RUBY_VERSION < "2.6.0"
43
- ERB.new(content, trim_mode: '-<>').result
46
+ def create_settings_file
47
+ settings_file = 'config/settings.yml'
48
+ template_file = File.expand_path("../templates/#{settings_file}.erb", __FILE__)
49
+ content = File.read(template_file)
50
+ locals = {
51
+ project_name: project_name.capitalize,
52
+ default_port: "<%= ENV.fetch('PORT', 3000) %>"
53
+ }
54
+ File.write settings_file,
55
+ erb(content, locals)
56
+ end
57
+
58
+ def erb(content, locals = {})
59
+ return ERB.new(content, 0, '%-<>').result_with_hash(locals) if RUBY_VERSION < "2.6.0"
60
+ ERB.new(content, trim_mode: '-<>').result_with_hash(locals)
44
61
  end
45
62
 
46
63
  def copy_templates
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require 'rack'
2
3
 
3
4
  module Shaf
4
5
  module Command
@@ -16,7 +17,10 @@ module Shaf
16
17
  def call
17
18
  Settings.port = options[:port] if options[:port]
18
19
  bootstrap
19
- App.run!
20
+ Rack::Server.start(
21
+ app: App,
22
+ Port: Settings.port
23
+ )
20
24
  end
21
25
  end
22
26
  end
@@ -11,6 +11,7 @@ gem 'sinatra-sequel'
11
11
  gem 'bcrypt'
12
12
  gem 'hal_presenter'
13
13
  gem 'redcarpet'
14
+ gem 'yard'
14
15
 
15
16
  group :production, :development do
16
17
  gem 'pg'