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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/iana_link_relations.csv.gz +0 -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/app.rb +32 -7
- 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/authenticator.rb +56 -0
- data/lib/shaf/command/base.rb +4 -0
- data/lib/shaf/command/console.rb +1 -1
- data/lib/shaf/command/generate.rb +5 -2
- data/lib/shaf/command/new.rb +24 -7
- data/lib/shaf/command/server.rb +5 -1
- data/lib/shaf/command/templates/Gemfile.erb +1 -0
- data/{templates/config/settings.yml → lib/shaf/command/templates/config/settings.yml.erb} +9 -12
- data/lib/shaf/errors.rb +11 -0
- 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 +95 -137
- data/lib/shaf/extensions/symbolic_routes.rb +9 -19
- data/lib/shaf/extensions.rb +3 -3
- data/lib/shaf/formable/builder.rb +58 -19
- data/lib/shaf/formable/form.rb +14 -11
- data/lib/shaf/formable.rb +10 -24
- data/lib/shaf/generator/base.rb +84 -3
- data/lib/shaf/generator/controller.rb +30 -42
- data/lib/shaf/generator/doc.rb +17 -0
- data/lib/shaf/generator/forms.rb +11 -14
- data/lib/shaf/generator/helper.rb +2 -1
- data/lib/shaf/generator/migration/add_column.rb +0 -4
- data/lib/shaf/generator/migration/add_index.rb +0 -4
- data/lib/shaf/generator/migration/base.rb +15 -3
- data/lib/shaf/generator/migration/create_table.rb +0 -4
- data/lib/shaf/generator/migration/drop_column.rb +0 -4
- data/lib/shaf/generator/migration/rename_column.rb +0 -4
- data/lib/shaf/generator/migration/type.rb +4 -26
- data/lib/shaf/generator/migration/types.rb +45 -16
- data/lib/shaf/generator/model.rb +29 -15
- data/lib/shaf/generator/policy.rb +8 -14
- data/lib/shaf/generator/profile.rb +47 -0
- data/lib/shaf/generator/scaffold.rb +6 -9
- data/lib/shaf/generator/serializer.rb +64 -98
- data/lib/shaf/generator/templates/api/controller.rb.erb +13 -13
- data/lib/shaf/generator/templates/api/forms.rb.erb +2 -2
- data/lib/shaf/generator/templates/api/model.rb.erb +1 -1
- 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 +3 -3
- data/lib/shaf/generator/templates/spec/integration_spec.rb.erb +15 -16
- data/lib/shaf/generator/templates/spec/serializer_spec.rb.erb +5 -5
- data/lib/shaf/generator.rb +2 -0
- data/lib/shaf/helpers/authentication.rb +79 -0
- data/lib/shaf/helpers/paginate.rb +1 -1
- data/lib/shaf/helpers/payload.rb +14 -34
- data/lib/shaf/helpers/vary.rb +8 -0
- data/lib/shaf/helpers.rb +4 -0
- data/lib/shaf/logger.rb +12 -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/parser.rb +65 -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/profile.rb +115 -0
- data/lib/shaf/profiles/shaf_basic.rb +20 -0
- data/lib/shaf/profiles/shaf_error.rb +49 -0
- data/lib/shaf/profiles/shaf_form.rb +110 -0
- data/lib/shaf/profiles.rb +46 -0
- data/lib/shaf/registrable_factory.rb +62 -32
- data/lib/shaf/responder/alps_json.rb +25 -0
- data/lib/shaf/responder/base.rb +20 -17
- data/lib/shaf/responder/hal.rb +66 -5
- data/lib/shaf/responder/html.rb +43 -16
- data/lib/shaf/responder/problem_json.rb +2 -2
- data/lib/shaf/responder.rb +41 -2
- data/lib/shaf/router.rb +65 -12
- data/lib/shaf/serializer.rb +35 -0
- data/lib/shaf/settings.rb +25 -12
- 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 +26 -14
- data/lib/shaf/spec/payload_utils.rb +2 -2
- data/lib/shaf/spec.rb +1 -0
- data/lib/shaf/supported_http_methods.rb +15 -0
- data/lib/shaf/tasks/routes_task.rb +14 -17
- data/lib/shaf/tasks.rb +0 -1
- data/lib/shaf/upgrade/manifest.rb +11 -2
- data/lib/shaf/upgrade/package.rb +73 -45
- 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/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/lib/shaf/yard.rb +34 -0
- data/lib/shaf.rb +6 -0
- data/templates/Rakefile +0 -6
- data/templates/api/controllers/base_controller.rb +0 -12
- 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/root_serializer.rb +0 -11
- 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/authentication.rb +18 -0
- data/templates/config/initializers/db_migrations.rb +2 -2
- data/templates/config/initializers/logging.rb +2 -2
- data/templates/config/initializers/middleware.rb +6 -0
- data/templates/config/initializers.rb +52 -8
- data/templates/config.ru +1 -1
- data/templates/spec/spec_helper.rb +5 -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/1.6.1.tar.gz +0 -0
- data/upgrades/2.0.0.tar.gz +0 -0
- data/upgrades/3.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
- data.tar.gz.sig +2 -2
- metadata +143 -38
- metadata.gz.sig +0 -0
- data/lib/shaf/api_doc/comment.rb +0 -27
- data/lib/shaf/api_doc/document.rb +0 -137
- data/lib/shaf/extensions/current_user.rb +0 -48
- data/lib/shaf/middleware.rb +0 -1
- data/lib/shaf/responder/hal_serializable.rb +0 -64
- 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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: aa31b5bc9a9cc80cc5b34c5a3f6101eb17ebf8166bd1a6ae46f0008709c3fa0f
|
|
4
|
+
data.tar.gz: d4fc21aa271c30bb3e52bbc65571917beb0b16029e7ee764fc8705a5d4197ca8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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 '
|
|
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
|
-
|
|
8
|
+
instance.run!
|
|
8
9
|
end
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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'
|
data/lib/shaf/command/base.rb
CHANGED
data/lib/shaf/command/console.rb
CHANGED
|
@@ -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)
|
|
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
|
-
|
|
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
|
data/lib/shaf/command/new.rb
CHANGED
|
@@ -10,21 +10,26 @@ module Shaf
|
|
|
10
10
|
usage 'new PROJECT_NAME'
|
|
11
11
|
|
|
12
12
|
def call
|
|
13
|
-
|
|
14
|
-
if
|
|
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
|
|
20
|
-
Dir.chdir(
|
|
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
|
|
42
|
-
|
|
43
|
-
|
|
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
|
data/lib/shaf/command/server.rb
CHANGED
|
@@ -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
|
-
|
|
20
|
+
Rack::Server.start(
|
|
21
|
+
app: App,
|
|
22
|
+
Port: Settings.port
|
|
23
|
+
)
|
|
20
24
|
end
|
|
21
25
|
end
|
|
22
26
|
end
|