forest_liana 6.0.0.pre.beta.1 → 6.0.0.pre.beta.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/controllers/forest_liana/application_controller.rb +1 -7
- data/app/controllers/forest_liana/authentication_controller.rb +122 -0
- data/app/controllers/forest_liana/base_controller.rb +4 -0
- data/app/controllers/forest_liana/router.rb +2 -2
- data/app/controllers/forest_liana/sessions_controller.rb +1 -1
- data/app/controllers/forest_liana/stats_controller.rb +5 -5
- data/app/helpers/forest_liana/adapter_helper.rb +1 -1
- data/app/serializers/forest_liana/schema_serializer.rb +2 -2
- data/app/services/forest_liana/apimap_sorter.rb +1 -1
- data/app/services/forest_liana/authentication.rb +59 -0
- data/app/services/forest_liana/authorization_getter.rb +12 -20
- data/app/services/forest_liana/forest_api_requester.rb +14 -5
- data/app/services/forest_liana/ip_whitelist_checker.rb +1 -1
- data/app/services/forest_liana/login_handler.rb +3 -11
- data/app/services/forest_liana/oidc_client_manager.rb +34 -0
- data/app/services/forest_liana/oidc_configuration_retriever.rb +12 -0
- data/app/services/forest_liana/oidc_dynamic_client_registrator.rb +67 -0
- data/app/services/forest_liana/permissions_checker.rb +1 -1
- data/app/services/forest_liana/query_stat_getter.rb +5 -5
- data/app/services/forest_liana/token.rb +27 -0
- data/config/initializers/error-messages.rb +20 -0
- data/config/routes.rb +5 -0
- data/lib/forest_liana.rb +1 -0
- data/lib/forest_liana/bootstrapper.rb +1 -1
- data/lib/forest_liana/collection.rb +2 -2
- data/lib/forest_liana/engine.rb +9 -0
- data/lib/forest_liana/json_printer.rb +1 -1
- data/lib/forest_liana/version.rb +1 -1
- data/spec/dummy/config/initializers/forest_liana.rb +1 -0
- data/spec/requests/authentications_spec.rb +107 -0
- data/spec/requests/sessions_spec.rb +55 -0
- data/spec/services/forest_liana/schema_adapter_spec.rb +1 -1
- metadata +54 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f2f88ccbd15c18a78b06d08ad2a17dfa2075d2b586728033cbe03564c95dd92e
|
4
|
+
data.tar.gz: e78f4892dc9ea07b8dbabb67d3183920f8ef41b6cb7ebe1edd8cb3d0c3b7ee2b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2a1dfc618f2723448b2fea3b39a48be64fad619c87a3b7d9257e3be8292f1ae86632c38563a7c9d938fd9699a0d78bac50e521d390bb40077ab824996b987ed2
|
7
|
+
data.tar.gz: d1a6401e8e236b718f488e1a1b5641fb953a912c719a5ea1010f7f307c99e7b96fd9f153fc483ebd7dbef5861a5daf9c12e2bdc03f846b62850662ee1837209a
|
@@ -3,8 +3,6 @@ require 'csv'
|
|
3
3
|
|
4
4
|
module ForestLiana
|
5
5
|
class ApplicationController < ForestLiana::BaseController
|
6
|
-
REGEX_COOKIE_SESSION_TOKEN = /forest_session_token=([^;]*)/;
|
7
|
-
|
8
6
|
def self.papertrail?
|
9
7
|
Object.const_get('PaperTrail::Version').is_a?(Class) rescue false
|
10
8
|
end
|
@@ -64,7 +62,7 @@ module ForestLiana
|
|
64
62
|
token = request.headers['Authorization'].split.second
|
65
63
|
# NOTICE: Necessary for downloads authentication.
|
66
64
|
elsif request.headers['cookie']
|
67
|
-
match = REGEX_COOKIE_SESSION_TOKEN.match(request.headers['cookie'])
|
65
|
+
match = ForestLiana::Token::REGEX_COOKIE_SESSION_TOKEN.match(request.headers['cookie'])
|
68
66
|
token = match[1] if match && match[1]
|
69
67
|
end
|
70
68
|
|
@@ -97,10 +95,6 @@ module ForestLiana
|
|
97
95
|
end
|
98
96
|
end
|
99
97
|
|
100
|
-
def route_not_found
|
101
|
-
head :not_found
|
102
|
-
end
|
103
|
-
|
104
98
|
def internal_server_error
|
105
99
|
head :internal_server_error
|
106
100
|
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module ForestLiana
|
5
|
+
class AuthenticationController < ForestLiana::BaseController
|
6
|
+
START_AUTHENTICATION_ROUTE = 'authentication'
|
7
|
+
CALLBACK_AUTHENTICATION_ROUTE = 'authentication/callback'
|
8
|
+
LOGOUT_ROUTE = 'authentication/logout';
|
9
|
+
PUBLIC_ROUTES = [
|
10
|
+
"/#{START_AUTHENTICATION_ROUTE}",
|
11
|
+
"/#{CALLBACK_AUTHENTICATION_ROUTE}",
|
12
|
+
"/#{LOGOUT_ROUTE}",
|
13
|
+
]
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@authentication_service = ForestLiana::Authentication.new()
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_callback_url
|
20
|
+
URI.join(ForestLiana.application_url, "/forest/#{CALLBACK_AUTHENTICATION_ROUTE}").to_s
|
21
|
+
rescue => error
|
22
|
+
raise "application_url is not valid or not defined" if error.is_a?(ArgumentError)
|
23
|
+
end
|
24
|
+
|
25
|
+
def get_and_check_rendering_id
|
26
|
+
if !params.has_key?('renderingId')
|
27
|
+
raise ForestLiana::MESSAGES[:SERVER_TRANSACTION][:MISSING_RENDERING_ID]
|
28
|
+
end
|
29
|
+
|
30
|
+
rendering_id = params[:renderingId]
|
31
|
+
|
32
|
+
if !(rendering_id.instance_of?(String) || rendering_id.instance_of?(Numeric)) || (rendering_id.instance_of?(Numeric) && rendering_id.nan?)
|
33
|
+
raise ForestLiana::MESSAGES[:SERVER_TRANSACTION][:INVALID_RENDERING_ID]
|
34
|
+
end
|
35
|
+
|
36
|
+
return rendering_id.to_i
|
37
|
+
end
|
38
|
+
|
39
|
+
def start_authentication
|
40
|
+
begin
|
41
|
+
rendering_id = get_and_check_rendering_id()
|
42
|
+
callback_url = get_callback_url()
|
43
|
+
|
44
|
+
result = @authentication_service.start_authentication(
|
45
|
+
callback_url,
|
46
|
+
{ 'renderingId' => rendering_id },
|
47
|
+
)
|
48
|
+
|
49
|
+
redirect_to(result['authorization_url'])
|
50
|
+
rescue => error
|
51
|
+
render json: { errors: [{ status: 500, detail: error.message }] },
|
52
|
+
status: :internal_server_error, serializer: nil
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def authentication_callback
|
57
|
+
begin
|
58
|
+
callback_url = get_callback_url()
|
59
|
+
|
60
|
+
token = @authentication_service.verify_code_and_generate_token(
|
61
|
+
callback_url,
|
62
|
+
params,
|
63
|
+
)
|
64
|
+
|
65
|
+
response.set_cookie(
|
66
|
+
'forest_session_token',
|
67
|
+
{
|
68
|
+
value: token,
|
69
|
+
httponly: true,
|
70
|
+
secure: true,
|
71
|
+
expires: ForestLiana::Token.expiration_in_days,
|
72
|
+
samesite: 'none',
|
73
|
+
path: '/'
|
74
|
+
},
|
75
|
+
)
|
76
|
+
|
77
|
+
response_body = {
|
78
|
+
tokenData: JWT.decode(token, ForestLiana.auth_secret, true, { algorithm: 'HS256' })[0]
|
79
|
+
}
|
80
|
+
|
81
|
+
# The token is sent decoded, because we don't want to share the whole, signed token
|
82
|
+
# that is used to authenticate people
|
83
|
+
# but the token itself contains interesting values, such as its expiration date
|
84
|
+
response_body[:token] = token if !ForestLiana.application_url.start_with?('https://')
|
85
|
+
|
86
|
+
render json: response_body, status: 200
|
87
|
+
|
88
|
+
rescue => error
|
89
|
+
render json: { errors: [{ status: 500, detail: error.message }] },
|
90
|
+
status: :internal_server_error, serializer: nil
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def logout
|
95
|
+
begin
|
96
|
+
if cookies.has_key?(:forest_session_token)
|
97
|
+
forest_session_token = cookies[:forest_session_token]
|
98
|
+
|
99
|
+
if forest_session_token
|
100
|
+
response.set_cookie(
|
101
|
+
'forest_session_token',
|
102
|
+
{
|
103
|
+
value: forest_session_token,
|
104
|
+
httponly: true,
|
105
|
+
secure: true,
|
106
|
+
expires: Time.at(0),
|
107
|
+
samesite: 'none',
|
108
|
+
path: '/'
|
109
|
+
},
|
110
|
+
)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
render json: {}, status: 204
|
115
|
+
rescue => error
|
116
|
+
render json: { errors: [{ status: 500, detail: error.message }] },
|
117
|
+
status: :internal_server_error, serializer: nil
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|
@@ -7,7 +7,7 @@ class ForestLiana::Router
|
|
7
7
|
if resource.nil?
|
8
8
|
FOREST_LOGGER.error "Routing error: Resource not found for collection #{collection_name}."
|
9
9
|
FOREST_LOGGER.error "If this is a Smart Collection, please ensure your Smart Collection routes are defined before the mounted ForestLiana::Engine?"
|
10
|
-
ForestLiana::
|
10
|
+
ForestLiana::BaseController.action(:route_not_found).call(env)
|
11
11
|
else
|
12
12
|
begin
|
13
13
|
component_prefix = ForestLiana.component_prefix(resource)
|
@@ -40,7 +40,7 @@ class ForestLiana::Router
|
|
40
40
|
controller.action(action.to_sym).call(env)
|
41
41
|
rescue NoMethodError => exception
|
42
42
|
FOREST_LOGGER.error "Routing error: #{exception}\n#{exception.backtrace.join("\n\t")}"
|
43
|
-
ForestLiana::
|
43
|
+
ForestLiana::BaseController.action(:route_not_found).call(env)
|
44
44
|
end
|
45
45
|
end
|
46
46
|
end
|
@@ -85,7 +85,7 @@ module ForestLiana
|
|
85
85
|
# NOTICE: Set a cookie to ensure secure authentication using export feature.
|
86
86
|
# NOTICE: The token is empty at first authentication step if the 2FA option is active.
|
87
87
|
if reponse_data[:token]
|
88
|
-
response.set_cookie("forest_session_token", { value: reponse_data[:token], expires: (
|
88
|
+
response.set_cookie("forest_session_token", { value: reponse_data[:token], expires: (ForestLiana::Token.expiration_in_days) })
|
89
89
|
end
|
90
90
|
|
91
91
|
render(json: reponse_data, serializer: nil)
|
@@ -6,11 +6,11 @@ module ForestLiana
|
|
6
6
|
before_action :find_resource, except: [:get_with_live_query]
|
7
7
|
end
|
8
8
|
|
9
|
-
CHART_TYPE_VALUE = 'Value'
|
10
|
-
CHART_TYPE_PIE = 'Pie'
|
11
|
-
CHART_TYPE_LINE = 'Line'
|
12
|
-
CHART_TYPE_LEADERBOARD = 'Leaderboard'
|
13
|
-
CHART_TYPE_OBJECTIVE = 'Objective'
|
9
|
+
CHART_TYPE_VALUE = 'Value'
|
10
|
+
CHART_TYPE_PIE = 'Pie'
|
11
|
+
CHART_TYPE_LINE = 'Line'
|
12
|
+
CHART_TYPE_LEADERBOARD = 'Leaderboard'
|
13
|
+
CHART_TYPE_OBJECTIVE = 'Objective'
|
14
14
|
|
15
15
|
def get
|
16
16
|
case params[:type]
|
@@ -52,7 +52,7 @@ class ForestLiana::SchemaSerializer
|
|
52
52
|
@included << format_child_content('segments', segment_id, segment)
|
53
53
|
end
|
54
54
|
else
|
55
|
-
collection_serialized[:attributes][attribute.to_sym] = value
|
55
|
+
collection_serialized[:attributes][attribute.to_sym] = value
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
@@ -75,7 +75,7 @@ class ForestLiana::SchemaSerializer
|
|
75
75
|
}
|
76
76
|
|
77
77
|
object.each do |attribute, value|
|
78
|
-
child_serialized[:attributes][attribute.to_sym] = value
|
78
|
+
child_serialized[:attributes][attribute.to_sym] = value
|
79
79
|
end
|
80
80
|
|
81
81
|
child_serialized
|
@@ -60,7 +60,7 @@ module ForestLiana
|
|
60
60
|
def perform
|
61
61
|
begin
|
62
62
|
@apimap = reorder_keys_basic(@apimap)
|
63
|
-
sort_array_of_objects(@apimap['data'])
|
63
|
+
sort_array_of_objects(@apimap['data'])
|
64
64
|
@apimap['data'].map! do |collection|
|
65
65
|
collection = reorder_keys_child(collection)
|
66
66
|
collection['attributes'] = reorder_collection_attributes(collection['attributes'])
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module ForestLiana
|
2
|
+
class Authentication
|
3
|
+
def start_authentication(redirect_url, state)
|
4
|
+
client = ForestLiana::OidcClientManager.get_client_for_callback_url(redirect_url)
|
5
|
+
|
6
|
+
authorization_url = client.authorization_uri({
|
7
|
+
scope: 'openid email profile',
|
8
|
+
state: state.to_s,
|
9
|
+
})
|
10
|
+
|
11
|
+
{ 'authorization_url' => authorization_url }
|
12
|
+
end
|
13
|
+
|
14
|
+
def verify_code_and_generate_token(redirect_url, params)
|
15
|
+
client = ForestLiana::OidcClientManager.get_client_for_callback_url(redirect_url)
|
16
|
+
|
17
|
+
rendering_id = parse_state(params['state'])
|
18
|
+
client.authorization_code = params['code']
|
19
|
+
|
20
|
+
if Rails.env.development? || Rails.env.test?
|
21
|
+
OpenIDConnect.http_config do |config|
|
22
|
+
config.ssl_config.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
23
|
+
end
|
24
|
+
end
|
25
|
+
access_token_instance = client.access_token! 'none'
|
26
|
+
|
27
|
+
user = ForestLiana::AuthorizationGetter.authenticate(
|
28
|
+
rendering_id,
|
29
|
+
true,
|
30
|
+
{ :forest_token => access_token_instance.instance_variable_get(:@access_token) },
|
31
|
+
nil,
|
32
|
+
)
|
33
|
+
|
34
|
+
return ForestLiana::Token.create_token(user, rendering_id)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
def parse_state(state)
|
39
|
+
unless state
|
40
|
+
raise ForestLiana::MESSAGES[:SERVER_TRANSACTION][:INVALID_STATE_MISSING]
|
41
|
+
end
|
42
|
+
|
43
|
+
rendering_id = nil
|
44
|
+
|
45
|
+
begin
|
46
|
+
parsed_state = JSON.parse(state.gsub("'",'"').gsub('=>',':'))
|
47
|
+
rendering_id = parsed_state["renderingId"].to_s
|
48
|
+
rescue
|
49
|
+
raise ForestLiana::MESSAGES[:SERVER_TRANSACTION][:INVALID_STATE_FORMAT]
|
50
|
+
end
|
51
|
+
|
52
|
+
if rendering_id.nil?
|
53
|
+
raise ForestLiana::MESSAGES[:SERVER_TRANSACTION][:INVALID_STATE_RENDERING_ID]
|
54
|
+
end
|
55
|
+
|
56
|
+
return rendering_id
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -1,39 +1,31 @@
|
|
1
1
|
module ForestLiana
|
2
2
|
class AuthorizationGetter
|
3
|
-
def
|
4
|
-
@rendering_id = rendering_id
|
5
|
-
@use_google_authentication = use_google_authentication
|
6
|
-
@auth_data = auth_data
|
7
|
-
@two_factor_registration = two_factor_registration
|
8
|
-
|
9
|
-
@route = "/liana/v2/renderings/#{rendering_id}"
|
10
|
-
@route += use_google_authentication ? "/google-authorization" : "/authorization"
|
11
|
-
end
|
12
|
-
|
13
|
-
def perform
|
3
|
+
def self.authenticate(rendering_id, use_google_authentication, auth_data, two_factor_registration)
|
14
4
|
begin
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
headers = { '
|
5
|
+
route = "/liana/v2/renderings/#{rendering_id.to_s}/authorization"
|
6
|
+
|
7
|
+
if !use_google_authentication.nil?
|
8
|
+
headers = { 'forest-token' => auth_data[:forest_token] }
|
9
|
+
elsif !auth_data[:email].nil?
|
10
|
+
headers = { 'email' => auth_data[:email], 'password' => auth_data[:password] }
|
19
11
|
end
|
20
12
|
|
21
13
|
query_parameters = {}
|
22
14
|
|
23
|
-
|
15
|
+
unless two_factor_registration.nil?
|
24
16
|
query_parameters['two-factor-registration'] = true
|
25
17
|
end
|
26
18
|
|
27
19
|
response = ForestLiana::ForestApiRequester
|
28
|
-
.get(
|
20
|
+
.get(route, query: query_parameters, headers: headers)
|
29
21
|
|
30
|
-
if response.
|
31
|
-
body = JSON.parse(response.body)
|
22
|
+
if response.code.to_i == 200
|
23
|
+
body = JSON.parse(response.body, :symbolize_names => false)
|
32
24
|
user = body['data']['attributes']
|
33
25
|
user['id'] = body['data']['id']
|
34
26
|
user
|
35
27
|
else
|
36
|
-
|
28
|
+
unless use_google_authentication.nil?
|
37
29
|
raise "Cannot authorize the user using this google account. Forest API returned an #{Errors::HTTPErrorHelper.format(response)}"
|
38
30
|
else
|
39
31
|
raise "Cannot authorize the user using this email/password. Forest API returned an #{Errors::HTTPErrorHelper.format(response)}"
|
@@ -5,33 +5,42 @@ module ForestLiana
|
|
5
5
|
def self.get(route, query: nil, headers: {})
|
6
6
|
begin
|
7
7
|
HTTParty.get("#{forest_api_url}#{route}", {
|
8
|
+
:verify => Rails.env.production?,
|
8
9
|
headers: base_headers.merge(headers),
|
9
10
|
query: query,
|
10
11
|
}).response
|
11
12
|
rescue
|
12
|
-
raise
|
13
|
+
raise "Cannot reach Forest API at #{forest_api_url}#{route}, it seems to be down right now."
|
13
14
|
end
|
14
15
|
end
|
15
16
|
|
16
17
|
def self.post(route, body: nil, query: nil, headers: {})
|
17
18
|
begin
|
18
|
-
|
19
|
+
if route.start_with?('https://')
|
20
|
+
post_route = route
|
21
|
+
else
|
22
|
+
post_route = "#{forest_api_url}#{route}"
|
23
|
+
end
|
24
|
+
|
25
|
+
HTTParty.post(post_route, {
|
26
|
+
:verify => Rails.env.production?,
|
19
27
|
headers: base_headers.merge(headers),
|
20
28
|
query: query,
|
21
29
|
body: body.to_json,
|
22
30
|
}).response
|
23
31
|
rescue
|
24
|
-
raise
|
32
|
+
raise "Cannot reach Forest API at #{post_route}, it seems to be down right now."
|
25
33
|
end
|
26
34
|
end
|
27
35
|
|
28
36
|
private
|
29
37
|
|
30
38
|
def self.base_headers
|
31
|
-
{
|
39
|
+
base_headers = {
|
32
40
|
'Content-Type' => 'application/json',
|
33
|
-
'forest-secret-key' => ForestLiana.env_secret,
|
34
41
|
}
|
42
|
+
base_headers['forest-secret-key'] = ForestLiana.env_secret if !ForestLiana.env_secret.nil?
|
43
|
+
return base_headers
|
35
44
|
end
|
36
45
|
|
37
46
|
def self.forest_api_url
|
@@ -58,7 +58,7 @@ module ForestLiana
|
|
58
58
|
ip_range_maximum = (IPAddress rule['ip_maximum']).to_i
|
59
59
|
ip_value = (IPAddress ip).to_i
|
60
60
|
|
61
|
-
return ip_value >= ip_range_minimum && ip_value <= ip_range_maximum
|
61
|
+
return ip_value >= ip_range_minimum && ip_value <= ip_range_maximum
|
62
62
|
end
|
63
63
|
|
64
64
|
def self.is_ip_match_subnet(ip, subnet)
|
@@ -19,12 +19,12 @@ module ForestLiana
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def perform
|
22
|
-
user = ForestLiana::AuthorizationGetter.
|
22
|
+
user = ForestLiana::AuthorizationGetter.authenticate(
|
23
23
|
@rendering_id,
|
24
24
|
@use_google_authentication,
|
25
25
|
@auth_data,
|
26
26
|
@two_factor_registration
|
27
|
-
)
|
27
|
+
)
|
28
28
|
|
29
29
|
if user['two_factor_authentication_enabled']
|
30
30
|
if !@two_factor_token.nil?
|
@@ -93,15 +93,7 @@ module ForestLiana
|
|
93
93
|
end
|
94
94
|
|
95
95
|
def create_token(user, rendering_id)
|
96
|
-
|
97
|
-
id: user['id'],
|
98
|
-
email: user['email'],
|
99
|
-
first_name: user['first_name'],
|
100
|
-
last_name: user['last_name'],
|
101
|
-
team: user['teams'][0],
|
102
|
-
rendering_id: rendering_id,
|
103
|
-
exp: Time.now.to_i + 2.weeks.to_i
|
104
|
-
}, ForestLiana.auth_secret, 'HS256')
|
96
|
+
ForestLiana::Token.create_token(user, rendering_id)
|
105
97
|
end
|
106
98
|
end
|
107
99
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'openid_connect'
|
2
|
+
|
3
|
+
module ForestLiana
|
4
|
+
class OidcClientManager
|
5
|
+
def self.get_client_for_callback_url(callback_url)
|
6
|
+
begin
|
7
|
+
client_data = Rails.cache.read(callback_url) || nil
|
8
|
+
if client_data.nil?
|
9
|
+
configuration = ForestLiana::OidcConfigurationRetriever.retrieve()
|
10
|
+
|
11
|
+
client_credentials = ForestLiana::OidcDynamicClientRegistrator.register({
|
12
|
+
token_endpoint_auth_method: 'none',
|
13
|
+
redirect_uris: [callback_url],
|
14
|
+
registration_endpoint: configuration['registration_endpoint']
|
15
|
+
})
|
16
|
+
|
17
|
+
client_data = { :client_id => client_credentials['client_id'], :issuer => configuration['issuer'] }
|
18
|
+
Rails.cache.write(callback_url, client_data)
|
19
|
+
end
|
20
|
+
|
21
|
+
OpenIDConnect::Client.new(
|
22
|
+
identifier: client_data[:client_id],
|
23
|
+
redirect_uri: callback_url,
|
24
|
+
host: "#{client_data[:issuer].sub(/^https?\:\/\/(www.)?/,'')}",
|
25
|
+
authorization_endpoint: '/oidc/auth',
|
26
|
+
token_endpoint: '/oidc/token',
|
27
|
+
)
|
28
|
+
rescue => error
|
29
|
+
Rails.cache.delete(callback_url)
|
30
|
+
raise error
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ForestLiana
|
2
|
+
class OidcConfigurationRetriever
|
3
|
+
def self.retrieve()
|
4
|
+
response = ForestLiana::ForestApiRequester.get('/oidc/.well-known/openid-configuration')
|
5
|
+
if response.is_a?(Net::HTTPOK)
|
6
|
+
return JSON.parse(response.body)
|
7
|
+
else
|
8
|
+
raise ForestLiana::MESSAGES[:SERVER_TRANSACTION][:OIDC_CONFIGURATION_RETRIEVAL_FAILED]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'json/jwt'
|
3
|
+
|
4
|
+
module ForestLiana
|
5
|
+
class OidcDynamicClientRegistrator
|
6
|
+
def self.is_standard_body_error(response)
|
7
|
+
result = false
|
8
|
+
begin
|
9
|
+
jsonbody
|
10
|
+
|
11
|
+
if (!response['body'].is_a?(Object) || response['body'].is_a?(StringIO))
|
12
|
+
jsonbody = JSON.parse(response['body'])
|
13
|
+
else
|
14
|
+
jsonbody = response['body']
|
15
|
+
end
|
16
|
+
|
17
|
+
result = jsonbody['error'].is_a?(String) && jsonbody['error'].length > 0
|
18
|
+
|
19
|
+
if (result)
|
20
|
+
response['body'] = jsonbody
|
21
|
+
end
|
22
|
+
rescue
|
23
|
+
{}
|
24
|
+
end
|
25
|
+
|
26
|
+
return result
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.process_response(response, expected = {})
|
30
|
+
statusCode = expected[:statusCode] || 200
|
31
|
+
body = expected[:body] || true
|
32
|
+
|
33
|
+
if (response.code.to_i != statusCode.to_i)
|
34
|
+
if (is_standard_body_error(response))
|
35
|
+
raise response['body']
|
36
|
+
end
|
37
|
+
|
38
|
+
raise ForestLiana::MESSAGES[:SERVER_TRANSACTION][:REGISTRATION_FAILED] + response.body
|
39
|
+
end
|
40
|
+
|
41
|
+
if (body && !response.body)
|
42
|
+
raise ForestLiana::MESSAGES[:SERVER_TRANSACTION][:REGISTRATION_FAILED] + response.body
|
43
|
+
end
|
44
|
+
|
45
|
+
return response.body
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.authorization_header_value(token, tokenType = 'Bearer')
|
49
|
+
return "#{tokenType} #{token}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.register(metadata)
|
53
|
+
initial_access_token = ForestLiana.env_secret
|
54
|
+
|
55
|
+
response = ForestLiana::ForestApiRequester.post(
|
56
|
+
metadata[:registration_endpoint],
|
57
|
+
body: metadata,
|
58
|
+
headers: initial_access_token ? {
|
59
|
+
Authorization: authorization_header_value(initial_access_token),
|
60
|
+
} : {},
|
61
|
+
)
|
62
|
+
|
63
|
+
responseBody = process_response(response, { :statusCode => 201, :bearer => true })
|
64
|
+
return JSON.parse(responseBody)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -44,7 +44,7 @@ module ForestLiana
|
|
44
44
|
@allowed = @smart_action_permissions['allowed']
|
45
45
|
@users = @smart_action_permissions['users']
|
46
46
|
|
47
|
-
return @allowed && (@users.nil?|| @users.include?(@user_id.to_i))
|
47
|
+
return @allowed && (@users.nil?|| @users.include?(@user_id.to_i))
|
48
48
|
end
|
49
49
|
|
50
50
|
def collection_list_allowed?(scope_permissions)
|
@@ -2,11 +2,11 @@ module ForestLiana
|
|
2
2
|
class QueryStatGetter
|
3
3
|
attr_accessor :record
|
4
4
|
|
5
|
-
CHART_TYPE_VALUE = 'Value'
|
6
|
-
CHART_TYPE_PIE = 'Pie'
|
7
|
-
CHART_TYPE_LINE = 'Line'
|
8
|
-
CHART_TYPE_LEADERBOARD = 'Leaderboard'
|
9
|
-
CHART_TYPE_OBJECTIVE = 'Objective'
|
5
|
+
CHART_TYPE_VALUE = 'Value'
|
6
|
+
CHART_TYPE_PIE = 'Pie'
|
7
|
+
CHART_TYPE_LINE = 'Line'
|
8
|
+
CHART_TYPE_LEADERBOARD = 'Leaderboard'
|
9
|
+
CHART_TYPE_OBJECTIVE = 'Objective'
|
10
10
|
|
11
11
|
def initialize(params)
|
12
12
|
@params = params
|
@@ -0,0 +1,27 @@
|
|
1
|
+
EXPIRATION_IN_SECONDS = 14.days
|
2
|
+
|
3
|
+
module ForestLiana
|
4
|
+
class Token
|
5
|
+
REGEX_COOKIE_SESSION_TOKEN = /forest_session_token=([^;]*)/;
|
6
|
+
|
7
|
+
def self.expiration_in_days
|
8
|
+
Time.current + EXPIRATION_IN_SECONDS
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.expiration_in_seconds
|
12
|
+
return Time.now.to_i + EXPIRATION_IN_SECONDS
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.create_token(user, rendering_id)
|
16
|
+
return JWT.encode({
|
17
|
+
id: user['id'],
|
18
|
+
email: user['email'],
|
19
|
+
first_name: user['first_name'],
|
20
|
+
last_name: user['last_name'],
|
21
|
+
team: user['teams'][0],
|
22
|
+
rendering_id: rendering_id,
|
23
|
+
exp: expiration_in_seconds()
|
24
|
+
}, ForestLiana.auth_secret, 'HS256')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module ForestLiana
|
2
|
+
MESSAGES = {
|
3
|
+
CONFIGURATION: {
|
4
|
+
AUTH_SECRET_MISSING: "Your Forest authSecret seems to be missing. Can you check that you properly set a Forest authSecret in the Forest initializer?",
|
5
|
+
},
|
6
|
+
SERVER_TRANSACTION: {
|
7
|
+
SECRET_AND_RENDERINGID_INCONSISTENT: "Cannot retrieve the project you're trying to unlock. The envSecret and renderingId seems to be missing or inconsistent.",
|
8
|
+
SERVER_DOWN: "Cannot retrieve the data from the Forest server. Forest API seems to be down right now.",
|
9
|
+
SECRET_NOT_FOUND: "Cannot retrieve the data from the Forest server. Can you check that you properly copied the Forest envSecret in the Liana initializer?",
|
10
|
+
UNEXPECTED: "Cannot retrieve the data from the Forest server. An error occured in Forest API.",
|
11
|
+
INVALID_STATE_MISSING: "Invalid response from the authentication server: the state parameter is missing",
|
12
|
+
INVALID_STATE_FORMAT: "Invalid response from the authentication server: the state parameter is not at the right format",
|
13
|
+
INVALID_STATE_RENDERING_ID: "Invalid response from the authentication server: the state does not contain a renderingId",
|
14
|
+
MISSING_RENDERING_ID: "Authentication request must contain a renderingId",
|
15
|
+
INVALID_RENDERING_ID: "The parameter renderingId is not valid",
|
16
|
+
REGISTRATION_FAILED: "The registration to the authentication API failed, response: ",
|
17
|
+
OIDC_CONFIGURATION_RETRIEVAL_FAILED: "Failed to retrieve the provider's configuration.",
|
18
|
+
}
|
19
|
+
}
|
20
|
+
end
|
data/config/routes.rb
CHANGED
@@ -4,6 +4,11 @@ ForestLiana::Engine.routes.draw do
|
|
4
4
|
# Onboarding
|
5
5
|
get '/' => 'apimaps#index'
|
6
6
|
|
7
|
+
# Authentication
|
8
|
+
post 'authentication' => 'authentication#start_authentication'
|
9
|
+
get 'authentication/callback' => 'authentication#authentication_callback'
|
10
|
+
post 'authentication/logout' => 'authentication#logout'
|
11
|
+
|
7
12
|
# Session
|
8
13
|
post 'sessions' => 'sessions#create_with_password'
|
9
14
|
post 'sessions-google' => 'sessions#create_with_google'
|
data/lib/forest_liana.rb
CHANGED
@@ -148,7 +148,7 @@ module ForestLiana::Collection
|
|
148
148
|
# TODO: Remove once lianas prior to 2.0.0 are not supported anymore.
|
149
149
|
model = ForestLiana.models.find { |collection| collection.try(:table_name) == collection_name.to_s }
|
150
150
|
if model
|
151
|
-
collection_name_new = ForestLiana.name_for(model)
|
151
|
+
collection_name_new = ForestLiana.name_for(model)
|
152
152
|
FOREST_LOGGER.warn "DEPRECATION WARNING: Collection names are now based on the models " \
|
153
153
|
"names. Please rename the collection '#{collection_name.to_s}' of your Forest " \
|
154
154
|
"customisation in '#{collection_name_new}'."
|
@@ -158,7 +158,7 @@ module ForestLiana::Collection
|
|
158
158
|
# TODO: Remove once lianas prior to 2.0.0 are not supported anymore.
|
159
159
|
model = ForestLiana.names_old_overriden.invert[collection_name.to_s]
|
160
160
|
if model
|
161
|
-
collection_name_new = ForestLiana.name_for(model)
|
161
|
+
collection_name_new = ForestLiana.name_for(model)
|
162
162
|
FOREST_LOGGER.warn "DEPRECATION WARNING: Collection names are now based on the models " \
|
163
163
|
"names. Please rename the collection '#{collection_name.to_s}' of your Forest " \
|
164
164
|
"customisation in '#{collection_name_new}'."
|
data/lib/forest_liana/engine.rb
CHANGED
@@ -16,8 +16,17 @@ module ForestLiana
|
|
16
16
|
begin
|
17
17
|
rack_cors_class = Rack::Cors
|
18
18
|
rack_cors_class = 'Rack::Cors' if Rails::VERSION::MAJOR < 5
|
19
|
+
null_regex = Regexp.new(/\Anull\z/)
|
19
20
|
|
20
21
|
config.middleware.insert_before 0, rack_cors_class do
|
22
|
+
allow do
|
23
|
+
hostnames = [null_regex, 'localhost:4200', /\A.*\.forestadmin\.com\z/]
|
24
|
+
hostnames += ENV['CORS_ORIGINS'].split(',') if ENV['CORS_ORIGINS']
|
25
|
+
|
26
|
+
origins hostnames
|
27
|
+
resource ForestLiana::AuthenticationController::PUBLIC_ROUTES[1], headers: :any, methods: :any, credentials: true, max_age: 86400 # NOTICE: 1 day
|
28
|
+
end
|
29
|
+
|
21
30
|
allow do
|
22
31
|
hostnames = ['localhost:4200', /\A.*\.forestadmin\.com\z/]
|
23
32
|
hostnames += ENV['CORS_ORIGINS'].split(',') if ENV['CORS_ORIGINS']
|
@@ -20,7 +20,7 @@ module ForestLiana
|
|
20
20
|
result << ", "
|
21
21
|
end
|
22
22
|
|
23
|
-
result << pretty_print(item, is_primary_value ? "#{indentation} " : indentation)
|
23
|
+
result << pretty_print(item, is_primary_value ? "#{indentation} " : indentation)
|
24
24
|
end
|
25
25
|
|
26
26
|
result << "\n#{indentation}" if is_primary_value && !is_small
|
data/lib/forest_liana/version.rb
CHANGED
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
require 'openid_connect'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
describe "Authentications", type: :request do
|
6
|
+
before do
|
7
|
+
allow(ForestLiana::IpWhitelist).to receive(:retrieve) { true }
|
8
|
+
allow(ForestLiana::IpWhitelist).to receive(:is_ip_whitelist_retrieved) { true }
|
9
|
+
allow(ForestLiana::IpWhitelist).to receive(:is_ip_valid) { true }
|
10
|
+
allow(ForestLiana::OidcConfigurationRetriever).to receive(:retrieve) {
|
11
|
+
JSON.parse('{
|
12
|
+
"registration_endpoint": "https://api.forestadmin.com/oidc/registration",
|
13
|
+
"issuer": "api.forestadmin.com"
|
14
|
+
}', :symbolize_names => false)
|
15
|
+
}
|
16
|
+
allow(ForestLiana::ForestApiRequester).to receive(:post) {
|
17
|
+
instance_double(HTTParty::Response, body: '{ "client_id": "random_id" }', code: 201)
|
18
|
+
}
|
19
|
+
allow_any_instance_of(OpenIDConnect::Client).to receive(:access_token!) {
|
20
|
+
OpenIDConnect::AccessToken.new(access_token: 'THE-ACCESS-TOKEN', client: instance_double(OpenIDConnect::Client))
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
after do
|
25
|
+
Rails.cache.delete(URI.join(ForestLiana.application_url, ForestLiana::Engine.routes.url_helpers.authentication_callback_path).to_s)
|
26
|
+
end
|
27
|
+
|
28
|
+
headers = {
|
29
|
+
'Accept' => 'application/json',
|
30
|
+
'Content-Type' => 'application/json',
|
31
|
+
}
|
32
|
+
|
33
|
+
describe "POST /authentication" do
|
34
|
+
before() do
|
35
|
+
post ForestLiana::Engine.routes.url_helpers.authentication_path, { :renderingId => 42 }, :headers => headers
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should respond with a 302 code" do
|
39
|
+
expect(response).to have_http_status(302)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should return a valid authentication url" do
|
43
|
+
expect(response.headers['Location']).to eq('https://api.forestadmin.com/oidc/auth?client_id=random_id&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fforest%2Fauthentication%2Fcallback&response_type=code&scope=openid%20email%20profile&state=%7B%22renderingId%22%3D%3E42%7D')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "GET /authentication/callback" do
|
48
|
+
before() do
|
49
|
+
response = '{"data":{"id":666,"attributes":{"first_name":"Alice","last_name":"Doe","email":"alice@forestadmin.com","teams":[1,2,3]}}}'
|
50
|
+
allow(ForestLiana::ForestApiRequester).to receive(:get).with(
|
51
|
+
"/liana/v2/renderings/42/authorization", { :headers => { "forest-token" => "THE-ACCESS-TOKEN" }, :query=> {} }
|
52
|
+
).and_return(
|
53
|
+
instance_double(HTTParty::Response, :body => response, :code => 200)
|
54
|
+
)
|
55
|
+
|
56
|
+
get ForestLiana::Engine.routes.url_helpers.authentication_callback_path + "?code=THE-CODE&state=#{URI.escape('{"renderingId":42}', Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))}"
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should respond with a 200 code" do
|
60
|
+
expect(response).to have_http_status(200)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should return a valid authentication token" do
|
64
|
+
session_cookie = response.headers['set-cookie']
|
65
|
+
expect(session_cookie).to match(/^forest_session_token=[^;]+; path=\/; expires=[^;]+; secure; HttpOnly$/)
|
66
|
+
|
67
|
+
token = session_cookie.match(/^forest_session_token=([^;]+);/)[1]
|
68
|
+
decoded = JWT.decode(token, ForestLiana.auth_secret, true, { algorithm: 'HS256' })[0]
|
69
|
+
|
70
|
+
expected_token_data = {
|
71
|
+
"id" => 666,
|
72
|
+
"email" => 'alice@forestadmin.com',
|
73
|
+
"rendering_id" => "42",
|
74
|
+
"first_name" => 'Alice',
|
75
|
+
"last_name" => 'Doe',
|
76
|
+
"team" => 1,
|
77
|
+
}
|
78
|
+
|
79
|
+
expect(decoded).to include(expected_token_data)
|
80
|
+
expect(JSON.parse(response.body, :symbolize_names => true)).to eq({ token: token, tokenData: decoded.deep_symbolize_keys! })
|
81
|
+
expect(response).to have_http_status(200)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "POST /authentication/logout" do
|
86
|
+
before() do
|
87
|
+
cookies['forest_session_token'] = {
|
88
|
+
value: 'eyJhbGciOiJIUzI1NiJ9.eyJpZCI6NjY2LCJlbWFpbCI6ImFsaWNlQGZvcmVzdGFkbWluLmNvbSIsImZpcnN0X25hbWUiOiJBbGljZSIsImxhc3RfbmFtZSI6IkRvZSIsInRlYW0iOjEsInJlbmRlcmluZ19pZCI6IjQyIiwiZXhwIjoxNjA4MDQ5MTI2fQ.5xaMxjUjE3wKldBsj3wW0BP9GHnnMqQi2Kpde8cIHEw',
|
89
|
+
path: '/',
|
90
|
+
expires: Time.now.to_i + 14.days,
|
91
|
+
secure: true,
|
92
|
+
httponly: true
|
93
|
+
}
|
94
|
+
post ForestLiana::Engine.routes.url_helpers.authentication_logout_path, { :renderingId => 42 }, :headers => headers
|
95
|
+
cookies.delete('forest_session_token')
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should respond with a 204 code" do
|
99
|
+
expect(response).to have_http_status(204)
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should invalidate token from browser" do
|
103
|
+
invalidated_session_cookie = response.headers['set-cookie']
|
104
|
+
expect(invalidated_session_cookie).to match(/^forest_session_token=[^;]+; path=\/; expires=Thu, 01 Jan 1970 00:00:00 -0000; secure; HttpOnly$/)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
require 'openid_connect'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
RSpec.describe "Authentications", type: :request do
|
6
|
+
before() do
|
7
|
+
allow(ForestLiana::IpWhitelist).to receive(:retrieve) { true }
|
8
|
+
allow(ForestLiana::IpWhitelist).to receive(:is_ip_whitelist_retrieved) { true }
|
9
|
+
allow(ForestLiana::IpWhitelist).to receive(:is_ip_valid) { true }
|
10
|
+
|
11
|
+
body = '{"data":{"id":"654","type":"users","attributes":{"email":"user@email.com","first_name":"FirstName","last_name":"LastName","teams":["Operations"]}},"relationships":{"renderings":{"data":[{"id":1,"type":"renderings"}]}}}'
|
12
|
+
allow(ForestLiana::ForestApiRequester).to receive(:get).with(
|
13
|
+
"/liana/v2/renderings/42/authorization", { :headers => { "forest-token" => "google-access-token" }, :query=> {} }
|
14
|
+
).and_return(
|
15
|
+
instance_double(HTTParty::Response, :body => body, :code => 200)
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
after() do
|
20
|
+
Rails.cache.delete(URI.join(ForestLiana.application_url, ForestLiana::Engine.routes.url_helpers.authentication_callback_path).to_s)
|
21
|
+
end
|
22
|
+
|
23
|
+
headers = {
|
24
|
+
'Accept' => 'application/json',
|
25
|
+
'Content-Type' => 'application/json',
|
26
|
+
}
|
27
|
+
|
28
|
+
describe "POST /forest/sessions-google" do
|
29
|
+
before() do
|
30
|
+
post ForestLiana::Engine.routes.url_helpers.sessions_google_path, { :renderingId => 42, :forestToken => "google-access-token" }, :headers => headers
|
31
|
+
end
|
32
|
+
|
33
|
+
it "should respond with a 200 code" do
|
34
|
+
expect(response).to have_http_status(200)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should return a valid authentication token" do
|
38
|
+
response_body = JSON.parse(response.body, :symbolize_names => true)
|
39
|
+
expect(response_body).to have_key(:token)
|
40
|
+
|
41
|
+
token = response_body[:token]
|
42
|
+
decoded = JWT.decode(token, ForestLiana.auth_secret, true, { algorithm: 'HS256' })[0]
|
43
|
+
|
44
|
+
expected_token_data = {
|
45
|
+
"id" => '654',
|
46
|
+
"email" => 'user@email.com',
|
47
|
+
"first_name" => 'FirstName',
|
48
|
+
"last_name" => 'LastName',
|
49
|
+
"rendering_id" => "42",
|
50
|
+
"team" => 'Operations'
|
51
|
+
}
|
52
|
+
expect(decoded).to include(expected_token_data);
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: forest_liana
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 6.0.0.pre.beta.
|
4
|
+
version: 6.0.0.pre.beta.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sandro Munda
|
@@ -178,6 +178,48 @@ dependencies:
|
|
178
178
|
- - ">="
|
179
179
|
- !ruby/object:Gem::Version
|
180
180
|
version: '0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: json
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - ">="
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0'
|
188
|
+
type: :runtime
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: json-jwt
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - ">="
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: '0'
|
202
|
+
type: :runtime
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - ">="
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0'
|
209
|
+
- !ruby/object:Gem::Dependency
|
210
|
+
name: openid_connect
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
212
|
+
requirements:
|
213
|
+
- - ">="
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: '0'
|
216
|
+
type: :runtime
|
217
|
+
prerelease: false
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - ">="
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: '0'
|
181
223
|
description: Forest is a modern admin interface that works on all major web frameworks.
|
182
224
|
forest_liana is the gem that makes Forest admin work on any Rails application (Rails
|
183
225
|
>= 4.0).
|
@@ -197,6 +239,7 @@ files:
|
|
197
239
|
- app/controllers/forest_liana/apimaps_controller.rb
|
198
240
|
- app/controllers/forest_liana/application_controller.rb
|
199
241
|
- app/controllers/forest_liana/associations_controller.rb
|
242
|
+
- app/controllers/forest_liana/authentication_controller.rb
|
200
243
|
- app/controllers/forest_liana/base_controller.rb
|
201
244
|
- app/controllers/forest_liana/devise_controller.rb
|
202
245
|
- app/controllers/forest_liana/intercom_controller.rb
|
@@ -230,6 +273,7 @@ files:
|
|
230
273
|
- app/serializers/forest_liana/stripe_payment_serializer.rb
|
231
274
|
- app/serializers/forest_liana/stripe_subscription_serializer.rb
|
232
275
|
- app/services/forest_liana/apimap_sorter.rb
|
276
|
+
- app/services/forest_liana/authentication.rb
|
233
277
|
- app/services/forest_liana/authorization_getter.rb
|
234
278
|
- app/services/forest_liana/base_getter.rb
|
235
279
|
- app/services/forest_liana/belongs_to_updater.rb
|
@@ -251,6 +295,9 @@ files:
|
|
251
295
|
- app/services/forest_liana/login_handler.rb
|
252
296
|
- app/services/forest_liana/mixpanel_last_events_getter.rb
|
253
297
|
- app/services/forest_liana/objective_stat_getter.rb
|
298
|
+
- app/services/forest_liana/oidc_client_manager.rb
|
299
|
+
- app/services/forest_liana/oidc_configuration_retriever.rb
|
300
|
+
- app/services/forest_liana/oidc_dynamic_client_registrator.rb
|
254
301
|
- app/services/forest_liana/operator_date_interval_parser.rb
|
255
302
|
- app/services/forest_liana/permissions_checker.rb
|
256
303
|
- app/services/forest_liana/permissions_getter.rb
|
@@ -275,11 +322,13 @@ files:
|
|
275
322
|
- app/services/forest_liana/stripe_sources_getter.rb
|
276
323
|
- app/services/forest_liana/stripe_subscription_getter.rb
|
277
324
|
- app/services/forest_liana/stripe_subscriptions_getter.rb
|
325
|
+
- app/services/forest_liana/token.rb
|
278
326
|
- app/services/forest_liana/two_factor_registration_confirmer.rb
|
279
327
|
- app/services/forest_liana/user_secret_creator.rb
|
280
328
|
- app/services/forest_liana/value_stat_getter.rb
|
281
329
|
- app/views/layouts/forest_liana/application.html.erb
|
282
330
|
- config/initializers/arel-helpers.rb
|
331
|
+
- config/initializers/error-messages.rb
|
283
332
|
- config/initializers/errors.rb
|
284
333
|
- config/initializers/logger.rb
|
285
334
|
- config/initializers/time_formats.rb
|
@@ -339,7 +388,9 @@ files:
|
|
339
388
|
- spec/helpers/forest_liana/query_helper_spec.rb
|
340
389
|
- spec/helpers/forest_liana/schema_helper_spec.rb
|
341
390
|
- spec/rails_helper.rb
|
391
|
+
- spec/requests/authentications_spec.rb
|
342
392
|
- spec/requests/resources_spec.rb
|
393
|
+
- spec/requests/sessions_spec.rb
|
343
394
|
- spec/services/forest_liana/apimap_sorter_spec.rb
|
344
395
|
- spec/services/forest_liana/filters_parser_spec.rb
|
345
396
|
- spec/services/forest_liana/ip_whitelist_checker_spec.rb
|
@@ -556,6 +607,8 @@ test_files:
|
|
556
607
|
- spec/services/forest_liana/apimap_sorter_spec.rb
|
557
608
|
- spec/services/forest_liana/filters_parser_spec.rb
|
558
609
|
- spec/spec_helper.rb
|
610
|
+
- spec/requests/sessions_spec.rb
|
611
|
+
- spec/requests/authentications_spec.rb
|
559
612
|
- spec/requests/resources_spec.rb
|
560
613
|
- spec/dummy/README.rdoc
|
561
614
|
- spec/dummy/app/views/layouts/application.html.erb
|