forest_liana 5.3.0 → 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/actions_controller.rb +0 -82
- 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/models/forest_liana/model/action.rb +1 -2
- data/app/serializers/forest_liana/schema_serializer.rb +2 -2
- data/app/services/forest_liana/apimap_sorter.rb +1 -2
- 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/resources_getter.rb +3 -3
- data/app/services/forest_liana/token.rb +27 -0
- data/config/initializers/error-messages.rb +20 -0
- data/config/routes.rb +5 -2
- data/lib/forest_liana.rb +1 -0
- data/lib/forest_liana/bootstrapper.rb +1 -20
- 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/schema_file_updater.rb +0 -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/apimap_sorter_spec.rb +4 -6
- metadata +57 -9
- data/app/helpers/forest_liana/is_same_data_structure_helper.rb +0 -44
- data/spec/helpers/forest_liana/is_same_data_structure_helper_spec.rb +0 -87
- data/spec/requests/actions_controller_spec.rb +0 -136
@@ -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
|
@@ -12,11 +12,11 @@ module ForestLiana
|
|
12
12
|
@collection = get_collection(@collection_name)
|
13
13
|
@fields_to_serialize = get_fields_to_serialize
|
14
14
|
@field_names_requested = field_names_requested
|
15
|
-
get_segment
|
16
|
-
compute_includes
|
15
|
+
get_segment()
|
16
|
+
compute_includes()
|
17
17
|
@search_query_builder = SearchQueryBuilder.new(@params, @includes, @collection)
|
18
18
|
|
19
|
-
prepare_query
|
19
|
+
prepare_query()
|
20
20
|
end
|
21
21
|
|
22
22
|
def self.get_ids_from_request(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'
|
@@ -57,6 +62,4 @@ ForestLiana::Engine.routes.draw do
|
|
57
62
|
|
58
63
|
# Smart Actions forms value
|
59
64
|
post 'actions/:action_name/values' => 'actions#values'
|
60
|
-
post 'actions/:action_name/hooks/load' => 'actions#load'
|
61
|
-
post 'actions/:action_name/hooks/change' => 'actions#change'
|
62
65
|
end
|
data/lib/forest_liana.rb
CHANGED
@@ -38,14 +38,6 @@ module ForestLiana
|
|
38
38
|
|
39
39
|
private
|
40
40
|
|
41
|
-
def get_collection(collection_name)
|
42
|
-
ForestLiana.apimap.find { |collection| collection.name.to_s == collection_name }
|
43
|
-
end
|
44
|
-
|
45
|
-
def get_action(collection, action_name)
|
46
|
-
collection.actions.find {|action| action.name == action_name}
|
47
|
-
end
|
48
|
-
|
49
41
|
def generate_apimap
|
50
42
|
create_apimap
|
51
43
|
require_lib_forest_liana
|
@@ -53,17 +45,6 @@ module ForestLiana
|
|
53
45
|
|
54
46
|
if Rails.env.development?
|
55
47
|
@collections_sent = ForestLiana.apimap.as_json
|
56
|
-
|
57
|
-
@collections_sent.each do |collection|
|
58
|
-
collection['actions'].each do |action|
|
59
|
-
c = get_collection(collection['name'])
|
60
|
-
a = get_action(c, action['name'])
|
61
|
-
load = !a.hooks.nil? && a.hooks.key?(:load) && a.hooks[:load].is_a?(Proc)
|
62
|
-
change = !a.hooks.nil? && a.hooks.key?(:change) && a.hooks[:change].is_a?(Hash) ? a.hooks[:change].keys : []
|
63
|
-
action['hooks'] = {:load => load, :change => change}
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
48
|
@meta_sent = ForestLiana.meta
|
68
49
|
SchemaFileUpdater.new(SCHEMA_FILENAME, @collections_sent, @meta_sent).perform()
|
69
50
|
else
|
@@ -640,7 +621,7 @@ module ForestLiana
|
|
640
621
|
end
|
641
622
|
|
642
623
|
def forest_url
|
643
|
-
ENV['FOREST_URL'] || 'https://api.forestadmin.com'
|
624
|
+
ENV['FOREST_URL'] || 'https://api.forestadmin.com'
|
644
625
|
end
|
645
626
|
|
646
627
|
def database_type
|
@@ -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
|