forest_liana 6.0.0.pre.beta.1 → 6.0.0.pre.beta.2
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
- 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
|