shaf 1.6.0 → 3.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.
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'