shaf 1.6.1 → 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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/iana_link_relations.csv.gz +0 -0
- data/lib/shaf.rb +6 -0
- data/lib/shaf/alps/attribute_serializer.rb +41 -0
- data/lib/shaf/alps/json_serializer.rb +50 -0
- data/lib/shaf/alps/relation_serializer.rb +70 -0
- data/lib/shaf/api_doc/link_relations.rb +77 -0
- data/lib/shaf/authenticator.rb +56 -0
- data/lib/shaf/authenticator/base.rb +161 -0
- data/lib/shaf/authenticator/basic_auth.rb +25 -0
- data/lib/shaf/authenticator/challenge.rb +32 -0
- data/lib/shaf/authenticator/parameter.rb +31 -0
- data/lib/shaf/authenticator/request.rb +17 -0
- data/lib/shaf/command/console.rb +1 -1
- data/lib/shaf/command/generate.rb +5 -2
- data/lib/shaf/command/new.rb +20 -7
- data/lib/shaf/command/templates/Gemfile.erb +1 -0
- data/{templates/config/settings.yml → lib/shaf/command/templates/config/settings.yml.erb} +1 -5
- data/lib/shaf/errors.rb +11 -0
- data/lib/shaf/extensions.rb +3 -3
- data/lib/shaf/extensions/api_routes.rb +60 -0
- data/lib/shaf/extensions/authorize.rb +11 -9
- data/lib/shaf/extensions/log.rb +1 -1
- data/lib/shaf/extensions/resource_uris.rb +139 -63
- data/lib/shaf/extensions/symbolic_routes.rb +22 -19
- data/lib/shaf/formable.rb +1 -2
- data/lib/shaf/formable/form.rb +1 -1
- data/lib/shaf/generator.rb +2 -0
- data/lib/shaf/generator/base.rb +2 -3
- data/lib/shaf/generator/controller.rb +11 -7
- data/lib/shaf/generator/doc.rb +17 -0
- data/lib/shaf/generator/forms.rb +1 -0
- data/lib/shaf/generator/helper.rb +2 -1
- data/lib/shaf/generator/migration/base.rb +7 -3
- data/lib/shaf/generator/migration/type.rb +4 -26
- data/lib/shaf/generator/migration/types.rb +45 -16
- data/lib/shaf/generator/model.rb +1 -2
- data/lib/shaf/generator/profile.rb +52 -0
- data/lib/shaf/generator/serializer.rb +38 -73
- data/lib/shaf/generator/templates/api/policy.rb.erb +2 -2
- data/lib/shaf/generator/templates/api/profile.rb.erb +16 -0
- data/lib/shaf/generator/templates/api/serializer.rb.erb +2 -2
- data/lib/shaf/generator/templates/spec/integration_spec.rb.erb +1 -2
- data/lib/shaf/generator/templates/spec/serializer_spec.rb.erb +5 -5
- data/lib/shaf/helpers.rb +4 -0
- data/lib/shaf/helpers/authentication.rb +79 -0
- data/lib/shaf/helpers/payload.rb +14 -34
- data/lib/shaf/helpers/vary.rb +8 -0
- data/lib/shaf/logger.rb +12 -0
- data/lib/shaf/parser.rb +65 -0
- data/lib/shaf/parser/base.rb +44 -0
- data/lib/shaf/parser/form_data.rb +15 -0
- data/lib/shaf/parser/json.rb +26 -0
- data/lib/shaf/profile.rb +110 -0
- data/lib/shaf/profile/attribute.rb +29 -0
- data/lib/shaf/profile/evaluator.rb +46 -0
- data/lib/shaf/profile/relation.rb +29 -0
- data/lib/shaf/profile/unique_id.rb +58 -0
- data/lib/shaf/profiles.rb +42 -0
- data/lib/shaf/profiles/shaf_basic.rb +20 -0
- data/lib/shaf/profiles/shaf_error.rb +48 -0
- data/lib/shaf/profiles/shaf_form.rb +109 -0
- data/lib/shaf/responder.rb +41 -2
- data/lib/shaf/responder/alps_json.rb +25 -0
- data/lib/shaf/responder/base.rb +20 -17
- data/lib/shaf/responder/hal.rb +65 -4
- data/lib/shaf/responder/html.rb +43 -16
- data/lib/shaf/responder/problem_json.rb +1 -1
- data/lib/shaf/serializer.rb +31 -0
- data/lib/shaf/settings.rb +25 -12
- data/lib/shaf/spec.rb +1 -0
- data/lib/shaf/spec/authenticator.rb +13 -0
- data/lib/shaf/spec/base.rb +1 -1
- data/lib/shaf/spec/http_method_utils.rb +1 -1
- data/lib/shaf/spec/integration_spec.rb +25 -13
- data/lib/shaf/spec/payload_utils.rb +2 -2
- data/lib/shaf/supported_http_methods.rb +15 -0
- data/lib/shaf/tasks/api_doc_task.rb +24 -3
- data/lib/shaf/tasks/routes_task.rb +14 -17
- data/lib/shaf/upgrade/manifest.rb +11 -2
- data/lib/shaf/upgrade/package.rb +73 -43
- data/lib/shaf/upgrade/version.rb +11 -10
- data/lib/shaf/utils.rb +19 -5
- data/lib/shaf/version.rb +3 -1
- data/lib/shaf/yard.rb +34 -0
- data/lib/shaf/yard/attribute_method_handler.rb +19 -0
- data/lib/shaf/yard/attribute_object.rb +30 -0
- data/lib/shaf/yard/base_method_handler.rb +30 -0
- data/lib/shaf/yard/link_method_handler.rb +39 -0
- data/lib/shaf/yard/link_object.rb +60 -0
- data/lib/shaf/yard/nested_attributes.rb +37 -0
- data/lib/shaf/yard/parser.rb +64 -0
- data/lib/shaf/yard/profile_method_handler.rb +51 -0
- data/lib/shaf/yard/profile_object.rb +21 -0
- data/lib/shaf/yard/resource_object.rb +55 -0
- data/lib/shaf/yard/serializer_handler.rb +27 -0
- data/templates/api/controllers/base_controller.rb +0 -10
- data/templates/api/controllers/docs_controller.rb +5 -3
- data/templates/api/controllers/root_controller.rb +7 -1
- data/templates/api/policies/base_policy.rb +2 -0
- data/templates/api/serializers/base_serializer.rb +1 -3
- data/templates/api/serializers/error_serializer.rb +1 -5
- data/templates/api/serializers/form_serializer.rb +1 -5
- data/templates/api/serializers/validation_error_serializer.rb +1 -5
- data/templates/config/bootstrap.rb +1 -2
- data/templates/config/directories.rb +52 -44
- data/templates/config/helpers.rb +1 -1
- data/templates/config/initializers.rb +52 -8
- data/templates/config/initializers/authentication.rb +18 -0
- data/templates/config/initializers/db_migrations.rb +2 -2
- data/templates/config/initializers/logging.rb +2 -2
- data/templates/spec/spec_helper.rb +2 -0
- data/upgrades/0.5.0.tar.gz +0 -0
- data/upgrades/1.0.4.tar.gz +0 -0
- data/upgrades/1.1.0.tar.gz +0 -0
- data/upgrades/2.0.0.tar.gz +0 -0
- data/yard_templates/api_doc/doc_index/html/body.erb +3 -0
- data/yard_templates/api_doc/doc_index/setup.rb +8 -0
- data/yard_templates/api_doc/html/css/api-doc.css +222 -0
- data/yard_templates/api_doc/html/favicon.ico +0 -0
- data/yard_templates/api_doc/html/js/switch_tab.js +17 -0
- data/yard_templates/api_doc/html/setup.rb +59 -0
- data/yard_templates/api_doc/layout/html/footer.erb +3 -0
- data/yard_templates/api_doc/layout/html/header.erb +7 -0
- data/yard_templates/api_doc/layout/html/layout.erb +13 -0
- data/yard_templates/api_doc/layout/setup.rb +24 -0
- data/yard_templates/api_doc/profile/html/attributes.erb +10 -0
- data/yard_templates/api_doc/profile/html/profile.erb +6 -0
- data/yard_templates/api_doc/profile/html/relations.erb +10 -0
- data/yard_templates/api_doc/profile/setup.rb +38 -0
- data/yard_templates/api_doc/profile_attribute/html/attribute.erb +23 -0
- data/yard_templates/api_doc/profile_attribute/setup.rb +21 -0
- data/yard_templates/api_doc/profile_relation/html/relation.erb +37 -0
- data/yard_templates/api_doc/profile_relation/setup.rb +41 -0
- data/yard_templates/api_doc/resource/html/attributes.erb +10 -0
- data/yard_templates/api_doc/resource/html/profile.erb +14 -0
- data/yard_templates/api_doc/resource/html/relations.erb +10 -0
- data/yard_templates/api_doc/resource/html/resource.erb +5 -0
- data/yard_templates/api_doc/resource/setup.rb +56 -0
- data/yard_templates/api_doc/resource_attribute/html/attribute.erb +23 -0
- data/yard_templates/api_doc/resource_attribute/setup.rb +20 -0
- data/yard_templates/api_doc/resource_relation/html/relation.erb +47 -0
- data/yard_templates/api_doc/resource_relation/setup.rb +80 -0
- data/yard_templates/api_doc/setup.rb +31 -0
- data/yard_templates/api_doc/sidebar/html/profile_list.erb +8 -0
- data/yard_templates/api_doc/sidebar/html/search.erb +7 -0
- data/yard_templates/api_doc/sidebar/html/serializer_list.erb +8 -0
- data/yard_templates/api_doc/sidebar/html/sidebar.erb +13 -0
- data/yard_templates/api_doc/sidebar/setup.rb +56 -0
- metadata +137 -30
- metadata.gz.sig +1 -3
- data/lib/shaf/extensions/current_user.rb +0 -48
- data/lib/shaf/responder/hal_serializable.rb +0 -64
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 87353c742213c291ee2def2d0cc55c6743ef429d52655824935eb131f43c4868
|
|
4
|
+
data.tar.gz: 7cec9186f28226651cbc07d0310d945bb901573e5d992cc980ff9863f36c6d09
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
@@ -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
|
+
|