insights-api-common 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 +7 -0
- data/LICENSE.txt +202 -0
- data/README.md +102 -0
- data/Rakefile +18 -0
- data/app/models/authentication.rb +19 -0
- data/app/models/concerns/encryption_concern.rb +52 -0
- data/app/models/encryption.rb +13 -0
- data/lib/generators/shared_utilities/migration_generator.rb +79 -0
- data/lib/generators/shared_utilities/orm_helper.rb +25 -0
- data/lib/generators/shared_utilities/templates/migration.rb +27 -0
- data/lib/generators/shared_utilities/templates/migration_existing.rb +28 -0
- data/lib/insights.rb +1 -0
- data/lib/insights/api/common.rb +12 -0
- data/lib/insights/api/common/application_controller_mixins/api_doc.rb +39 -0
- data/lib/insights/api/common/application_controller_mixins/common.rb +27 -0
- data/lib/insights/api/common/application_controller_mixins/exception_handling.rb +41 -0
- data/lib/insights/api/common/application_controller_mixins/openapi_enabled.rb +13 -0
- data/lib/insights/api/common/application_controller_mixins/parameters.rb +134 -0
- data/lib/insights/api/common/application_controller_mixins/request_body_validation.rb +48 -0
- data/lib/insights/api/common/application_controller_mixins/request_parameter_validation.rb +29 -0
- data/lib/insights/api/common/application_controller_mixins/request_path.rb +70 -0
- data/lib/insights/api/common/engine.rb +20 -0
- data/lib/insights/api/common/entitlement.rb +37 -0
- data/lib/insights/api/common/error_document.rb +29 -0
- data/lib/insights/api/common/filter.rb +175 -0
- data/lib/insights/api/common/graphql.rb +127 -0
- data/lib/insights/api/common/graphql/associated_records.rb +44 -0
- data/lib/insights/api/common/graphql/association_loader.rb +35 -0
- data/lib/insights/api/common/graphql/generator.rb +148 -0
- data/lib/insights/api/common/graphql/templates/model_type.erb +35 -0
- data/lib/insights/api/common/graphql/templates/query_type.erb +49 -0
- data/lib/insights/api/common/graphql/templates/schema.erb +6 -0
- data/lib/insights/api/common/graphql/types/big_int.rb +23 -0
- data/lib/insights/api/common/graphql/types/date_time.rb +16 -0
- data/lib/insights/api/common/graphql/types/query_filter.rb +16 -0
- data/lib/insights/api/common/graphql/types/query_sort_by.rb +16 -0
- data/lib/insights/api/common/inflections.rb +28 -0
- data/lib/insights/api/common/logging.rb +17 -0
- data/lib/insights/api/common/metrics.rb +39 -0
- data/lib/insights/api/common/middleware/web_server_metrics.rb +62 -0
- data/lib/insights/api/common/open_api.rb +2 -0
- data/lib/insights/api/common/open_api/docs.rb +54 -0
- data/lib/insights/api/common/open_api/docs/component_collection.rb +67 -0
- data/lib/insights/api/common/open_api/docs/doc_v3.rb +102 -0
- data/lib/insights/api/common/open_api/docs/object_definition.rb +39 -0
- data/lib/insights/api/common/open_api/generator.rb +520 -0
- data/lib/insights/api/common/open_api/serializer.rb +31 -0
- data/lib/insights/api/common/option_redirect_enhancements.rb +23 -0
- data/lib/insights/api/common/paginated_response.rb +108 -0
- data/lib/insights/api/common/rbac/access.rb +66 -0
- data/lib/insights/api/common/rbac/acl.rb +74 -0
- data/lib/insights/api/common/rbac/policies.rb +33 -0
- data/lib/insights/api/common/rbac/query_shared_resource.rb +45 -0
- data/lib/insights/api/common/rbac/roles.rb +77 -0
- data/lib/insights/api/common/rbac/seed.rb +140 -0
- data/lib/insights/api/common/rbac/service.rb +67 -0
- data/lib/insights/api/common/rbac/share_resource.rb +60 -0
- data/lib/insights/api/common/rbac/unshare_resource.rb +32 -0
- data/lib/insights/api/common/rbac/utilities.rb +30 -0
- data/lib/insights/api/common/request.rb +111 -0
- data/lib/insights/api/common/routing.rb +26 -0
- data/lib/insights/api/common/user.rb +48 -0
- data/lib/insights/api/common/version.rb +7 -0
- data/lib/tasks/insights/api/common_tasks.rake +4 -0
- data/spec/support/default_as_json.rb +17 -0
- data/spec/support/rbac_shared_contexts.rb +44 -0
- data/spec/support/requests_spec_helper.rb +7 -0
- data/spec/support/service_spec_helper.rb +26 -0
- data/spec/support/user_header_spec_helper.rb +68 -0
- metadata +403 -0
@@ -0,0 +1,140 @@
|
|
1
|
+
module Insights
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
module RBAC
|
5
|
+
require 'rbac-api-client'
|
6
|
+
|
7
|
+
class Seed
|
8
|
+
def initialize(seed_file, user_file = nil)
|
9
|
+
@acl_data = YAML.load_file(seed_file)
|
10
|
+
@request = Insights::API::Common::Request.current || create_request(user_file)
|
11
|
+
end
|
12
|
+
|
13
|
+
def process
|
14
|
+
Insights::API::Common::Request.with_request(@request) do
|
15
|
+
create_groups
|
16
|
+
create_roles
|
17
|
+
create_policies
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def create_groups
|
24
|
+
current = current_groups
|
25
|
+
names = current.collect(&:name)
|
26
|
+
group = RBACApiClient::Group.new
|
27
|
+
begin
|
28
|
+
Service.call(RBACApiClient::GroupApi) do |api_instance|
|
29
|
+
@acl_data['groups'].each do |grp|
|
30
|
+
next if names.include?(grp['name'])
|
31
|
+
|
32
|
+
Rails.logger.info("Creating #{grp['name']}")
|
33
|
+
group.name = grp['name']
|
34
|
+
group.description = grp['description']
|
35
|
+
api_instance.create_group(group)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
rescue RBACApiClient::ApiError => e
|
39
|
+
Rails.logger.error("Exception when calling GroupApi->create_group: #{e}")
|
40
|
+
raise
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def current_groups
|
45
|
+
Service.call(RBACApiClient::GroupApi) do |api|
|
46
|
+
Service.paginate(api, :list_groups, {}).to_a
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def create_roles
|
51
|
+
current = current_roles
|
52
|
+
names = current.collect(&:name)
|
53
|
+
role_in = RBACApiClient::RoleIn.new
|
54
|
+
begin
|
55
|
+
Service.call(RBACApiClient::RoleApi) do |api_instance|
|
56
|
+
@acl_data['roles'].each do |role|
|
57
|
+
next if names.include?(role['name'])
|
58
|
+
|
59
|
+
role_in.name = role['name']
|
60
|
+
role_in.access = []
|
61
|
+
role['access'].each do |obj|
|
62
|
+
access = RBACApiClient::Access.new
|
63
|
+
access.permission = obj['permission']
|
64
|
+
access.resource_definitions = create_rds(obj)
|
65
|
+
role_in.access << access
|
66
|
+
end
|
67
|
+
api_instance.create_roles(role_in)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
rescue RBACApiClient::ApiError => e
|
71
|
+
Rails.logger.error("Exception when calling RoleApi->create_roles: #{e}")
|
72
|
+
raise
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def create_rds(obj)
|
77
|
+
obj.fetch('resource_definitions', []).collect do |item|
|
78
|
+
RBACApiClient::ResourceDefinition.new.tap do |rd|
|
79
|
+
rd.attribute_filter = RBACApiClient::ResourceDefinitionFilter.new.tap do |rdf|
|
80
|
+
rdf.key = item['attribute_filter']['key']
|
81
|
+
rdf.value = item['attribute_filter']['value']
|
82
|
+
rdf.operation = item['attribute_filter']['operation']
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def current_roles
|
89
|
+
Service.call(RBACApiClient::RoleApi) do |api|
|
90
|
+
Service.paginate(api, :list_roles, {}).to_a
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def create_policies
|
95
|
+
names = current_policies.collect(&:name)
|
96
|
+
groups = current_groups
|
97
|
+
roles = current_roles
|
98
|
+
policy_in = RBACApiClient::PolicyIn.new
|
99
|
+
begin
|
100
|
+
Service.call(RBACApiClient::PolicyApi) do |api_instance|
|
101
|
+
@acl_data['policies'].each do |policy|
|
102
|
+
next if names.include?(policy['name'])
|
103
|
+
|
104
|
+
policy_in.name = policy['name']
|
105
|
+
policy_in.description = policy['description']
|
106
|
+
policy_in.group = find_uuid('Group', groups, policy['group']['name'])
|
107
|
+
policy_in.roles = [find_uuid('Role', roles, policy['role']['name'])]
|
108
|
+
api_instance.create_policies(policy_in)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
rescue RBACApiClient::ApiError => e
|
112
|
+
Rails.logger.error("Exception when calling PolicyApi->create_policies: #{e}")
|
113
|
+
raise
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def current_policies
|
118
|
+
Service.call(RBACApiClient::PolicyApi) do |api|
|
119
|
+
Service.paginate(api, :list_policies, {}).to_a
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def find_uuid(type, data, name)
|
124
|
+
result = data.detect { |item| item.name == name }
|
125
|
+
raise "#{type} #{name} not found in RBAC service" unless result
|
126
|
+
|
127
|
+
result.uuid
|
128
|
+
end
|
129
|
+
|
130
|
+
def create_request(user_file)
|
131
|
+
raise "File #{user_file} not found" unless File.exist?(user_file)
|
132
|
+
|
133
|
+
user = YAML.load_file(user_file)
|
134
|
+
{:headers => {'x-rh-identity' => Base64.strict_encode64(user.to_json)}, :original_url => '/'}
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Insights
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
module RBAC
|
5
|
+
require 'rbac-api-client'
|
6
|
+
|
7
|
+
class Service
|
8
|
+
def self.call(klass)
|
9
|
+
setup
|
10
|
+
yield init(klass)
|
11
|
+
rescue RBACApiClient::ApiError => err
|
12
|
+
Rails.logger.error("RBACApiClient::ApiError #{err.message} ")
|
13
|
+
raise
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.paginate(obj, method, pagination_options, *method_args)
|
17
|
+
Enumerator.new do |enum|
|
18
|
+
opts = { :limit => 10, :offset => 0 }.merge(pagination_options)
|
19
|
+
count = nil
|
20
|
+
fetched = 0
|
21
|
+
begin
|
22
|
+
loop do
|
23
|
+
args = [method_args, opts].flatten.compact
|
24
|
+
result = obj.send(method, *args)
|
25
|
+
count ||= result.meta.count
|
26
|
+
opts[:offset] = opts[:offset] + result.data.count
|
27
|
+
result.data.each do |element|
|
28
|
+
enum.yield element
|
29
|
+
end
|
30
|
+
fetched += result.data.count
|
31
|
+
break if count == fetched || result.data.empty?
|
32
|
+
end
|
33
|
+
rescue StandardError => e
|
34
|
+
Rails.logger.error("Exception when calling pagination on #{method} #{e}")
|
35
|
+
raise
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private_class_method def self.setup
|
41
|
+
RBACApiClient.configure do |config|
|
42
|
+
config.host = ENV['RBAC_URL'] || 'localhost'
|
43
|
+
config.scheme = URI.parse(ENV['RBAC_URL']).try(:scheme) || 'http'
|
44
|
+
dev_credentials(config)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private_class_method def self.init(klass)
|
49
|
+
headers = Insights::API::Common::Request.current_forwardable
|
50
|
+
Rails.logger.info("Sending Headers to RBAC #{headers}")
|
51
|
+
klass.new.tap do |api|
|
52
|
+
api.api_client.default_headers = api.api_client.default_headers.merge(headers)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private_class_method def self.dev_credentials(config)
|
57
|
+
# Set up user/pass for basic auth if we're in dev and they exist.
|
58
|
+
if Rails.env.development?
|
59
|
+
config.username = ENV.fetch('DEV_USERNAME')
|
60
|
+
config.password = ENV.fetch('DEV_PASSWORD')
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Insights
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
module RBAC
|
5
|
+
class ShareResource
|
6
|
+
require 'rbac-api-client'
|
7
|
+
include Utilities
|
8
|
+
|
9
|
+
def initialize(options)
|
10
|
+
@app_name = options[:app_name]
|
11
|
+
@resource_name = options[:resource_name]
|
12
|
+
@permissions = options[:permissions]
|
13
|
+
@resource_ids = options[:resource_ids]
|
14
|
+
@group_uuids = SortedSet.new(options[:group_uuids])
|
15
|
+
@acls = RBAC::ACL.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def process
|
19
|
+
validate_groups
|
20
|
+
@roles = RBAC::Roles.new("#{@app_name}-#{@resource_name}-", 'account')
|
21
|
+
@group_uuids.each { |uuid| manage_roles_for_group(uuid) }
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def manage_roles_for_group(group_uuid)
|
28
|
+
@resource_ids.each do |resource_id|
|
29
|
+
name = unique_name(resource_id, group_uuid)
|
30
|
+
role = @roles.find(name)
|
31
|
+
role ? update_existing_role(role, resource_id) : add_new_role(name, group_uuid, resource_id)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def update_existing_role(role, resource_id)
|
36
|
+
role.access = @acls.add(role.access, resource_id, @permissions)
|
37
|
+
@roles.update(role) if role.access.present?
|
38
|
+
end
|
39
|
+
|
40
|
+
def add_new_role(name, group_uuid, resource_id)
|
41
|
+
acls = @acls.create(resource_id, @permissions)
|
42
|
+
role = @roles.add(name, acls)
|
43
|
+
add_policy(name, group_uuid, role.uuid)
|
44
|
+
end
|
45
|
+
|
46
|
+
def add_policy(name, group_uuid, role_uuid)
|
47
|
+
Service.call(RBACApiClient::PolicyApi) do |api_instance|
|
48
|
+
policy_in = RBACApiClient::PolicyIn.new
|
49
|
+
policy_in.name = name
|
50
|
+
policy_in.description = 'Shared Policy'
|
51
|
+
policy_in.group = group_uuid
|
52
|
+
policy_in.roles = [role_uuid]
|
53
|
+
api_instance.create_policies(policy_in)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Insights
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
module RBAC
|
5
|
+
require 'rbac-api-client'
|
6
|
+
|
7
|
+
class UnshareResource < ShareResource
|
8
|
+
attr_accessor :count
|
9
|
+
|
10
|
+
def initialize(options)
|
11
|
+
@count = 0
|
12
|
+
super
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def manage_roles_for_group(group_uuid)
|
18
|
+
@resource_ids.each do |resource_id|
|
19
|
+
name = unique_name(resource_id, group_uuid)
|
20
|
+
role = @roles.find(name)
|
21
|
+
next unless role
|
22
|
+
|
23
|
+
role.access = @acls.remove(role.access, resource_id, @permissions)
|
24
|
+
role.access.present? ? @roles.update(role) : @roles.delete(role)
|
25
|
+
@count += 1
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Insights
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
module RBAC
|
5
|
+
module Utilities
|
6
|
+
def validate_groups
|
7
|
+
Service.call(RBACApiClient::GroupApi) do |api|
|
8
|
+
uuids = SortedSet.new
|
9
|
+
Service.paginate(api, :list_groups, {}).each { |group| uuids << group.uuid }
|
10
|
+
missing = @group_uuids - uuids
|
11
|
+
raise Insights::API::Common::InvalidParameter, "The following group uuids are missing #{missing.to_a.join(",")}" unless missing.empty?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def unique_name(resource_id, group_id)
|
16
|
+
"#{@app_name}-#{@resource_name}-#{resource_id}-group-#{group_id}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def parse_ids_from_name(name)
|
20
|
+
@regexp ||= Regexp.new("#{@app_name}-#{@resource_name}-(?<resource_id>.*)-group-(?<group_uuid>.*)")
|
21
|
+
result = @regexp.match(name)
|
22
|
+
if result
|
23
|
+
[result[:resource_id], result[:group_uuid]]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module Insights
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
class RequestNotSet < ArgumentError
|
5
|
+
def initialize
|
6
|
+
super("Current request has not been set")
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class InvalidParameter < StandardError
|
11
|
+
end
|
12
|
+
|
13
|
+
class Request
|
14
|
+
REQUEST_ID_KEY = "x-rh-insights-request-id".freeze
|
15
|
+
IDENTITY_KEY = 'x-rh-identity'.freeze
|
16
|
+
PERSONA_KEY = 'x-rh-persona'.freeze
|
17
|
+
FORWARDABLE_HEADER_KEYS = [REQUEST_ID_KEY, IDENTITY_KEY, PERSONA_KEY].freeze
|
18
|
+
OPTIONAL_AUTH_PATHS = [
|
19
|
+
%r{\A/api/v[0-9]+(\.[0-9]+)?/openapi.json\z},
|
20
|
+
%r{\A/api/[^/]+/v[0-9]+(\.[0-9]+)?/openapi.json\z}
|
21
|
+
].freeze
|
22
|
+
|
23
|
+
def self.current
|
24
|
+
Thread.current[:current_request]
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.current!
|
28
|
+
current || raise(RequestNotSet)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.current=(request)
|
32
|
+
Thread.current[:current_request] =
|
33
|
+
case request
|
34
|
+
when ActionDispatch::Request
|
35
|
+
new(:headers => request.headers, :original_url => request.original_url)
|
36
|
+
when Hash
|
37
|
+
new(request)
|
38
|
+
when Request, nil
|
39
|
+
request
|
40
|
+
else
|
41
|
+
raise ArgumentError, 'Not an Insights::API::Common::Request or ActionDispatch::Request Class, Hash, or nil'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.with_request(request)
|
46
|
+
saved = current
|
47
|
+
self.current = request
|
48
|
+
yield current
|
49
|
+
ensure
|
50
|
+
self.current = saved
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.current_forwardable
|
54
|
+
current!.forwardable
|
55
|
+
end
|
56
|
+
|
57
|
+
attr_reader :headers, :original_url
|
58
|
+
|
59
|
+
def initialize(headers:, original_url:, **_kwargs)
|
60
|
+
headers = from_hash(headers) if headers.kind_of?(Hash)
|
61
|
+
@headers, @original_url = headers, original_url
|
62
|
+
end
|
63
|
+
|
64
|
+
def request_id
|
65
|
+
headers.fetch(REQUEST_ID_KEY, nil)
|
66
|
+
end
|
67
|
+
|
68
|
+
def identity
|
69
|
+
@identity ||= JSON.parse(Base64.decode64(headers.fetch(IDENTITY_KEY)))
|
70
|
+
rescue KeyError
|
71
|
+
raise IdentityError, "x-rh-identity not found"
|
72
|
+
end
|
73
|
+
|
74
|
+
def user
|
75
|
+
@user ||= User.new(identity)
|
76
|
+
end
|
77
|
+
|
78
|
+
def entitlement
|
79
|
+
@entitlement ||= Entitlement.new(identity)
|
80
|
+
end
|
81
|
+
|
82
|
+
def to_h
|
83
|
+
{:headers => forwardable, :original_url => original_url}
|
84
|
+
end
|
85
|
+
|
86
|
+
def forwardable
|
87
|
+
FORWARDABLE_HEADER_KEYS.each_with_object({}) do |key, hash|
|
88
|
+
hash[key] = headers[key] if headers.key?(key)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def required_auth?
|
93
|
+
!optional_auth?
|
94
|
+
end
|
95
|
+
|
96
|
+
def optional_auth?
|
97
|
+
uri_path = URI.parse(original_url).path
|
98
|
+
OPTIONAL_AUTH_PATHS.any? { |optional_auth_path_regex| optional_auth_path_regex.match(uri_path) }
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def from_hash(hash)
|
104
|
+
ActionDispatch::Http::Headers.from_hash({}).tap do |headers|
|
105
|
+
hash.each { |k, v| headers.add(k, v) }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Insights
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
class Routing
|
5
|
+
attr_reader :route_mapper
|
6
|
+
|
7
|
+
def initialize(route_mapper)
|
8
|
+
@route_mapper = route_mapper
|
9
|
+
end
|
10
|
+
|
11
|
+
def redirect_major_version(version, prefix, via: [:delete, :get, :options, :patch, :post])
|
12
|
+
route_mapper.match(
|
13
|
+
"/#{version.split('.').first}/*path(.:format)",
|
14
|
+
:format => false,
|
15
|
+
:via => via,
|
16
|
+
:to => route_mapper.redirect(
|
17
|
+
:path => "/#{prefix}/#{version}/%{path}",
|
18
|
+
:only_path => true,
|
19
|
+
:status => 302
|
20
|
+
)
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|