shaf 1.5.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (165) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/iana_link_relations.csv.gz +0 -0
  5. data/lib/shaf.rb +6 -0
  6. data/lib/shaf/alps/attribute_serializer.rb +41 -0
  7. data/lib/shaf/alps/json_serializer.rb +50 -0
  8. data/lib/shaf/alps/relation_serializer.rb +70 -0
  9. data/lib/shaf/api_doc/link_relations.rb +77 -0
  10. data/lib/shaf/app.rb +12 -5
  11. data/lib/shaf/authenticator.rb +56 -0
  12. data/lib/shaf/authenticator/base.rb +161 -0
  13. data/lib/shaf/authenticator/basic_auth.rb +25 -0
  14. data/lib/shaf/authenticator/challenge.rb +32 -0
  15. data/lib/shaf/authenticator/parameter.rb +31 -0
  16. data/lib/shaf/authenticator/request.rb +17 -0
  17. data/lib/shaf/command/console.rb +1 -1
  18. data/lib/shaf/command/generate.rb +5 -2
  19. data/lib/shaf/command/new.rb +20 -7
  20. data/lib/shaf/command/templates/Gemfile.erb +1 -0
  21. data/{templates/config/settings.yml → lib/shaf/command/templates/config/settings.yml.erb} +1 -5
  22. data/lib/shaf/errors.rb +11 -0
  23. data/lib/shaf/extensions.rb +3 -3
  24. data/lib/shaf/extensions/api_routes.rb +60 -0
  25. data/lib/shaf/extensions/authorize.rb +11 -9
  26. data/lib/shaf/extensions/log.rb +1 -1
  27. data/lib/shaf/extensions/resource_uris.rb +139 -63
  28. data/lib/shaf/extensions/symbolic_routes.rb +22 -19
  29. data/lib/shaf/formable.rb +1 -2
  30. data/lib/shaf/formable/form.rb +1 -1
  31. data/lib/shaf/generator.rb +2 -0
  32. data/lib/shaf/generator/base.rb +2 -3
  33. data/lib/shaf/generator/controller.rb +11 -7
  34. data/lib/shaf/generator/doc.rb +17 -0
  35. data/lib/shaf/generator/forms.rb +1 -0
  36. data/lib/shaf/generator/helper.rb +2 -1
  37. data/lib/shaf/generator/migration/base.rb +7 -3
  38. data/lib/shaf/generator/migration/type.rb +4 -26
  39. data/lib/shaf/generator/migration/types.rb +45 -16
  40. data/lib/shaf/generator/model.rb +1 -2
  41. data/lib/shaf/generator/profile.rb +52 -0
  42. data/lib/shaf/generator/serializer.rb +38 -73
  43. data/lib/shaf/generator/templates/api/policy.rb.erb +2 -2
  44. data/lib/shaf/generator/templates/api/profile.rb.erb +16 -0
  45. data/lib/shaf/generator/templates/api/serializer.rb.erb +2 -2
  46. data/lib/shaf/generator/templates/spec/integration_spec.rb.erb +1 -2
  47. data/lib/shaf/generator/templates/spec/serializer_spec.rb.erb +5 -5
  48. data/lib/shaf/helpers.rb +4 -0
  49. data/lib/shaf/helpers/authentication.rb +79 -0
  50. data/lib/shaf/helpers/cache_control.rb +1 -2
  51. data/lib/shaf/helpers/json_html.rb +58 -18
  52. data/lib/shaf/helpers/payload.rb +27 -41
  53. data/lib/shaf/helpers/vary.rb +8 -0
  54. data/lib/shaf/logger.rb +12 -0
  55. data/lib/shaf/parser.rb +65 -0
  56. data/lib/shaf/parser/base.rb +44 -0
  57. data/lib/shaf/parser/form_data.rb +15 -0
  58. data/lib/shaf/parser/json.rb +26 -0
  59. data/lib/shaf/profile.rb +110 -0
  60. data/lib/shaf/profile/attribute.rb +29 -0
  61. data/lib/shaf/profile/evaluator.rb +46 -0
  62. data/lib/shaf/profile/relation.rb +29 -0
  63. data/lib/shaf/profile/unique_id.rb +58 -0
  64. data/lib/shaf/profiles.rb +42 -0
  65. data/lib/shaf/profiles/shaf_basic.rb +20 -0
  66. data/lib/shaf/profiles/shaf_error.rb +48 -0
  67. data/lib/shaf/profiles/shaf_form.rb +109 -0
  68. data/lib/shaf/responder.rb +41 -2
  69. data/lib/shaf/responder/alps_json.rb +25 -0
  70. data/lib/shaf/responder/base.rb +20 -17
  71. data/lib/shaf/responder/hal.rb +62 -7
  72. data/lib/shaf/responder/html.rb +65 -9
  73. data/lib/shaf/responder/problem_json.rb +1 -1
  74. data/lib/shaf/serializer.rb +31 -0
  75. data/lib/shaf/settings.rb +25 -12
  76. data/lib/shaf/spec.rb +1 -0
  77. data/lib/shaf/spec/authenticator.rb +13 -0
  78. data/lib/shaf/spec/base.rb +1 -1
  79. data/lib/shaf/spec/http_method_utils.rb +1 -1
  80. data/lib/shaf/spec/integration_spec.rb +25 -13
  81. data/lib/shaf/spec/payload_utils.rb +2 -2
  82. data/lib/shaf/supported_http_methods.rb +15 -0
  83. data/lib/shaf/tasks/api_doc_task.rb +24 -3
  84. data/lib/shaf/tasks/routes_task.rb +14 -17
  85. data/lib/shaf/upgrade/manifest.rb +11 -2
  86. data/lib/shaf/upgrade/package.rb +78 -49
  87. data/lib/shaf/upgrade/version.rb +11 -10
  88. data/lib/shaf/utils.rb +19 -5
  89. data/lib/shaf/version.rb +3 -1
  90. data/lib/shaf/yard.rb +34 -0
  91. data/lib/shaf/yard/attribute_method_handler.rb +19 -0
  92. data/lib/shaf/yard/attribute_object.rb +30 -0
  93. data/lib/shaf/yard/base_method_handler.rb +30 -0
  94. data/lib/shaf/yard/link_method_handler.rb +39 -0
  95. data/lib/shaf/yard/link_object.rb +60 -0
  96. data/lib/shaf/yard/nested_attributes.rb +37 -0
  97. data/lib/shaf/yard/parser.rb +64 -0
  98. data/lib/shaf/yard/profile_method_handler.rb +51 -0
  99. data/lib/shaf/yard/profile_object.rb +21 -0
  100. data/lib/shaf/yard/resource_object.rb +55 -0
  101. data/lib/shaf/yard/serializer_handler.rb +27 -0
  102. data/templates/api/controllers/base_controller.rb +0 -10
  103. data/templates/api/controllers/docs_controller.rb +5 -3
  104. data/templates/api/controllers/root_controller.rb +7 -1
  105. data/templates/api/policies/base_policy.rb +2 -0
  106. data/templates/api/serializers/base_serializer.rb +1 -3
  107. data/templates/api/serializers/error_serializer.rb +1 -5
  108. data/templates/api/serializers/form_serializer.rb +1 -5
  109. data/templates/api/serializers/validation_error_serializer.rb +1 -5
  110. data/templates/config.ru +1 -1
  111. data/templates/config/bootstrap.rb +1 -2
  112. data/templates/config/directories.rb +52 -44
  113. data/templates/config/helpers.rb +1 -1
  114. data/templates/config/initializers.rb +52 -8
  115. data/templates/config/initializers/authentication.rb +18 -0
  116. data/templates/config/initializers/db_migrations.rb +2 -2
  117. data/templates/config/initializers/logging.rb +2 -2
  118. data/templates/frontend/assets/css/main.css +33 -1
  119. data/templates/frontend/views/headers.erb +20 -0
  120. data/templates/frontend/views/layout.erb +7 -1
  121. data/templates/frontend/views/payload.erb +1 -0
  122. data/templates/spec/spec_helper.rb +2 -0
  123. data/upgrades/0.5.0.tar.gz +0 -0
  124. data/upgrades/1.0.4.tar.gz +0 -0
  125. data/upgrades/1.1.0.tar.gz +0 -0
  126. data/upgrades/1.6.0.tar.gz +0 -0
  127. data/upgrades/1.6.1.tar.gz +0 -0
  128. data/upgrades/2.0.0.tar.gz +0 -0
  129. data/yard_templates/api_doc/doc_index/html/body.erb +3 -0
  130. data/yard_templates/api_doc/doc_index/setup.rb +8 -0
  131. data/yard_templates/api_doc/html/css/api-doc.css +222 -0
  132. data/yard_templates/api_doc/html/favicon.ico +0 -0
  133. data/yard_templates/api_doc/html/js/switch_tab.js +17 -0
  134. data/yard_templates/api_doc/html/setup.rb +59 -0
  135. data/yard_templates/api_doc/layout/html/footer.erb +3 -0
  136. data/yard_templates/api_doc/layout/html/header.erb +7 -0
  137. data/yard_templates/api_doc/layout/html/layout.erb +13 -0
  138. data/yard_templates/api_doc/layout/setup.rb +24 -0
  139. data/yard_templates/api_doc/profile/html/attributes.erb +10 -0
  140. data/yard_templates/api_doc/profile/html/profile.erb +6 -0
  141. data/yard_templates/api_doc/profile/html/relations.erb +10 -0
  142. data/yard_templates/api_doc/profile/setup.rb +38 -0
  143. data/yard_templates/api_doc/profile_attribute/html/attribute.erb +23 -0
  144. data/yard_templates/api_doc/profile_attribute/setup.rb +21 -0
  145. data/yard_templates/api_doc/profile_relation/html/relation.erb +37 -0
  146. data/yard_templates/api_doc/profile_relation/setup.rb +41 -0
  147. data/yard_templates/api_doc/resource/html/attributes.erb +10 -0
  148. data/yard_templates/api_doc/resource/html/profile.erb +14 -0
  149. data/yard_templates/api_doc/resource/html/relations.erb +10 -0
  150. data/yard_templates/api_doc/resource/html/resource.erb +5 -0
  151. data/yard_templates/api_doc/resource/setup.rb +56 -0
  152. data/yard_templates/api_doc/resource_attribute/html/attribute.erb +23 -0
  153. data/yard_templates/api_doc/resource_attribute/setup.rb +20 -0
  154. data/yard_templates/api_doc/resource_relation/html/relation.erb +47 -0
  155. data/yard_templates/api_doc/resource_relation/setup.rb +80 -0
  156. data/yard_templates/api_doc/setup.rb +31 -0
  157. data/yard_templates/api_doc/sidebar/html/profile_list.erb +8 -0
  158. data/yard_templates/api_doc/sidebar/html/search.erb +7 -0
  159. data/yard_templates/api_doc/sidebar/html/serializer_list.erb +8 -0
  160. data/yard_templates/api_doc/sidebar/html/sidebar.erb +13 -0
  161. data/yard_templates/api_doc/sidebar/setup.rb +56 -0
  162. metadata +140 -30
  163. metadata.gz.sig +1 -2
  164. data/lib/shaf/extensions/current_user.rb +0 -48
  165. data/lib/shaf/responder/hal_serializable.rb +0 -54
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 21af40642344c3526a2b34df81fe891ce407ac8ef822e125938d8ecc415fcba0
4
- data.tar.gz: 8be6b04b3a460979d6494fda33a5a88797b23d6c3266bb51e5505184b70707da
3
+ metadata.gz: 87353c742213c291ee2def2d0cc55c6743ef429d52655824935eb131f43c4868
4
+ data.tar.gz: 7cec9186f28226651cbc07d0310d945bb901573e5d992cc980ff9863f36c6d09
5
5
  SHA512:
6
- metadata.gz: 505ab287d131637d261d3391eeb58517cfe9aa6160d1e51bfb619544299bfea360b90a63ffb1ad0d2c70edaf4da7ca64c84f4de3d624a001c07d2eb0fdb27c10
7
- data.tar.gz: 8dc0a97ed2b062b8bbb85cbcdce50a624e42ca010c12638a998deb33d9e7d1438d18a04d58ac8cdb5339286f9744a2948f8c63e0f739636b61940c35b0b8f0db
6
+ metadata.gz: 7fb1521b3f4eba94982da604cd42996f9c744bda53cb04ba22fd92af02c687be0a3c3a1c22bd02a9f1478e633ef9d05e2334f212bcdb4a4a5aacb2bd9b203e61
7
+ data.tar.gz: 05eba3ff47be63a14be932312465410186407974b523c108e3bf759ec72bc94eaed3452571f397009261a12025243c19071bc7dea8b09078e0053284a7cd5dfd
checksums.yaml.gz.sig CHANGED
Binary file
data.tar.gz.sig CHANGED
Binary file
Binary file
data/lib/shaf.rb CHANGED
@@ -1,7 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'shaf/version'
4
+ require 'shaf/logger'
2
5
  require 'shaf/settings'
3
6
  require 'shaf/command'
4
7
  require 'shaf/app'
8
+ require 'shaf/supported_http_methods'
5
9
  require 'shaf/router'
6
10
  require 'shaf/errors'
7
11
  require 'shaf/formable'
@@ -9,3 +13,5 @@ require 'shaf/extensions'
9
13
  require 'shaf/helpers'
10
14
  require 'shaf/resource_doc'
11
15
  require 'shaf/upgrade'
16
+ require 'shaf/profiles'
17
+ require 'shaf/serializer'
@@ -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
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'tempfile'
5
+ require 'uri'
6
+ require 'csv'
7
+
8
+ module Shaf
9
+ module ApiDoc
10
+ class LinkRelations
11
+ class LinkRelation
12
+ attr_reader :name, :description, :reference, :notes
13
+
14
+ def initialize(name, description, reference, notes)
15
+ @name = name.to_sym
16
+ @description = description.freeze
17
+ @reference = reference.freeze
18
+ @notes = notes.freeze
19
+ end
20
+ end
21
+
22
+ class << self
23
+ IANA_URL = URI('https://www.iana.org/assignments/link-relations/link-relations-1.csv')
24
+
25
+ def all
26
+ relations.values
27
+ end
28
+
29
+ def [](key)
30
+ relations[key]
31
+ end
32
+
33
+ def []=(key, value)
34
+ relations[key] = value
35
+ end
36
+
37
+ def add(link_relation)
38
+ relations[link_relation.name] = link_relation
39
+ end
40
+
41
+ def load_iana
42
+ csv.each do |name, desc, ref, notes|
43
+ next if name == 'Relation Name'
44
+ add LinkRelation.new(name, desc, ref, notes)
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def relations
51
+ @relations ||= {}
52
+ end
53
+
54
+ def tmp_file_name
55
+ File.join(Dir.tmpdir, 'shaf_iana_link_relations')
56
+ end
57
+
58
+ def csv
59
+ if File.readable? tmp_file_name
60
+ content = File.read(tmp_file_name)
61
+ return CSV.new(content)
62
+ end
63
+
64
+ response = Net::HTTP.get_response(IANA_URL)
65
+
66
+ if response.code.to_i == 200
67
+ content = response.body
68
+ File.open(tmp_file_name, 'w') { |file| file.write(content) }
69
+ CSV.new(content)
70
+ else
71
+ Utils.iana_link_relations_csv
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
data/lib/shaf/app.rb CHANGED
@@ -3,16 +3,23 @@ require 'shaf/middleware'
3
3
  module Shaf
4
4
  class App
5
5
  class << self
6
+ # Either call `Shaf::App.run!`
6
7
  def run!
7
8
  app.run!
8
9
  end
9
10
 
11
+ # Or `run Shaf::App` (in config.ru)
12
+ def call(*args)
13
+ app.call(*args)
14
+ end
15
+
10
16
  def app
11
- Sinatra.new.tap do |app|
12
- app.set :port, Settings.port || 3000
13
- app.use Middleware::RequestId
14
- app.use Router
15
- end
17
+ @app ||=
18
+ Sinatra.new.tap do |app|
19
+ app.set :port, Settings.port || 3000
20
+ app.use Middleware::RequestId
21
+ app.use Router
22
+ end
16
23
  end
17
24
  end
18
25
  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'
@@ -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
+