Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
1
Merge Requests
1
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
nexedi
gitlab-ce
Commits
e6159431
Commit
e6159431
authored
Nov 01, 2018
by
Douglas Barbosa Alexandre
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Include full path while redirecting the user back to secondary
parent
ef3444e8
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
153 additions
and
33 deletions
+153
-33
ee/app/controllers/ee/sessions_controller.rb
ee/app/controllers/ee/sessions_controller.rb
+5
-1
ee/app/controllers/oauth/geo_auth_controller.rb
ee/app/controllers/oauth/geo_auth_controller.rb
+5
-4
ee/app/services/oauth2/logout_token_validation_service.rb
ee/app/services/oauth2/logout_token_validation_service.rb
+10
-5
ee/lib/gitlab/geo/oauth_session.rb
ee/lib/gitlab/geo/oauth_session.rb
+29
-3
ee/spec/controllers/oauth/geo_auth_controller_spec.rb
ee/spec/controllers/oauth/geo_auth_controller_spec.rb
+12
-3
ee/spec/lib/gitlab/geo/oauth_session_spec.rb
ee/spec/lib/gitlab/geo/oauth_session_spec.rb
+39
-8
ee/spec/services/oauth2/logout_token_validation_service_spec.rb
...c/services/oauth2/logout_token_validation_service_spec.rb
+53
-9
No files found.
ee/app/controllers/ee/sessions_controller.rb
View file @
e6159431
...
@@ -26,7 +26,11 @@ module EE
...
@@ -26,7 +26,11 @@ module EE
def
gitlab_geo_logout
def
gitlab_geo_logout
return
unless
::
Gitlab
::
Geo
.
secondary?
return
unless
::
Gitlab
::
Geo
.
secondary?
oauth
=
::
Gitlab
::
Geo
::
OauthSession
.
new
(
access_token:
session
[
:access_token
])
oauth
=
::
Gitlab
::
Geo
::
OauthSession
.
new
(
access_token:
session
[
:access_token
],
return_to:
safe_redirect_path_for_url
(
request
.
referer
)
)
@geo_logout_state
=
oauth
.
generate_logout_state
# rubocop:disable Gitlab/ModuleWithInstanceVariables
@geo_logout_state
=
oauth
.
generate_logout_state
# rubocop:disable Gitlab/ModuleWithInstanceVariables
end
end
...
...
ee/app/controllers/oauth/geo_auth_controller.rb
View file @
e6159431
...
@@ -11,7 +11,6 @@ class Oauth::GeoAuthController < ActionController::Base
...
@@ -11,7 +11,6 @@ class Oauth::GeoAuthController < ActionController::Base
redirect_to
oauth
.
authorize_url
(
redirect_uri:
oauth_geo_callback_url
,
state:
params
[
:state
])
redirect_to
oauth
.
authorize_url
(
redirect_uri:
oauth_geo_callback_url
,
state:
params
[
:state
])
end
end
# rubocop: disable CodeReuse/ActiveRecord
def
callback
def
callback
unless
oauth
.
oauth_state_valid?
unless
oauth
.
oauth_state_valid?
redirect_to
new_user_session_path
redirect_to
new_user_session_path
...
@@ -20,8 +19,7 @@ class Oauth::GeoAuthController < ActionController::Base
...
@@ -20,8 +19,7 @@ class Oauth::GeoAuthController < ActionController::Base
token
=
oauth
.
get_token
(
params
[
:code
],
redirect_uri:
oauth_geo_callback_url
)
token
=
oauth
.
get_token
(
params
[
:code
],
redirect_uri:
oauth_geo_callback_url
)
remote_user
=
oauth
.
authenticate_with_gitlab
(
token
)
remote_user
=
oauth
.
authenticate_with_gitlab
(
token
)
user
=
UserFinder
.
new
(
remote_user
[
'id'
]).
find_by_id
user
=
User
.
find_by
(
id:
remote_user
[
'id'
])
if
user
&&
bypass_sign_in
(
user
)
if
user
&&
bypass_sign_in
(
user
)
after_sign_in_with_gitlab
(
token
,
oauth
.
get_oauth_state_return_to
)
after_sign_in_with_gitlab
(
token
,
oauth
.
get_oauth_state_return_to
)
...
@@ -29,7 +27,6 @@ class Oauth::GeoAuthController < ActionController::Base
...
@@ -29,7 +27,6 @@ class Oauth::GeoAuthController < ActionController::Base
invalid_credentials
invalid_credentials
end
end
end
end
# rubocop: enable CodeReuse/ActiveRecord
def
logout
def
logout
logout
=
Oauth2
::
LogoutTokenValidationService
.
new
(
current_user
,
params
)
logout
=
Oauth2
::
LogoutTokenValidationService
.
new
(
current_user
,
params
)
...
@@ -51,6 +48,10 @@ class Oauth::GeoAuthController < ActionController::Base
...
@@ -51,6 +48,10 @@ class Oauth::GeoAuthController < ActionController::Base
def
after_sign_in_with_gitlab
(
token
,
return_to
)
def
after_sign_in_with_gitlab
(
token
,
return_to
)
session
[
:access_token
]
=
token
session
[
:access_token
]
=
token
# Prevent alert from popping up on the first page shown after authentication.
flash
[
:alert
]
=
nil
redirect_to
(
return_to
||
root_path
)
redirect_to
(
return_to
||
root_path
)
end
end
...
...
ee/app/services/oauth2/logout_token_validation_service.rb
View file @
e6159431
...
@@ -2,21 +2,21 @@ module Oauth2
...
@@ -2,21 +2,21 @@ module Oauth2
class
LogoutTokenValidationService
<
::
BaseService
class
LogoutTokenValidationService
<
::
BaseService
include
Gitlab
::
Utils
::
StrongMemoize
include
Gitlab
::
Utils
::
StrongMemoize
attr_reader
:stat
us
attr_reader
:stat
e
def
initialize
(
user
,
params
=
{})
def
initialize
(
user
,
params
=
{})
@params
=
params
@current_user
=
user
@current_user
=
user
@state
=
params
[
:state
]
end
end
def
execute
def
execute
return
error
(
'Access token not found'
)
unless
access_token
return
error
(
'Access token not found'
)
unless
access_token
.
present?
status
=
AccessTokenValidationService
.
new
(
access_token
).
validate
status
=
AccessTokenValidationService
.
new
(
access_token
).
validate
return
error
(
status
)
unless
status
==
AccessTokenValidationService
::
VALID
return
error
(
status
)
unless
status
==
AccessTokenValidationService
::
VALID
user
=
User
.
find
(
access_token
.
resource_owner_id
)
user
=
User
.
find
(
access_token
.
resource_owner_id
)
success
(
return_to:
geo_node_url
)
if
user
==
current_user
success
(
return_to:
user_return_to
)
if
user
==
current_user
end
end
private
private
...
@@ -32,7 +32,12 @@ module Oauth2
...
@@ -32,7 +32,12 @@ module Oauth2
end
end
def
oauth_session
def
oauth_session
@oauth_session
||=
Gitlab
::
Geo
::
OauthSession
.
new
(
state:
params
[
:state
])
@oauth_session
||=
Gitlab
::
Geo
::
OauthSession
.
new
(
state:
state
)
end
def
user_return_to
full_path
=
oauth_session
.
get_oauth_state_return_to_full_path
URI
.
join
(
geo_node_url
,
full_path
).
to_s
end
end
def
geo_node_url
def
geo_node_url
...
...
ee/lib/gitlab/geo/oauth_session.rb
View file @
e6159431
...
@@ -11,7 +11,6 @@ module Gitlab
...
@@ -11,7 +11,6 @@ module Gitlab
return
false
unless
state
return
false
unless
state
salt
,
hmac
,
return_to
=
state
.
split
(
':'
,
3
)
salt
,
hmac
,
return_to
=
state
.
split
(
':'
,
3
)
return
false
unless
return_to
return
false
unless
return_to
hmac
==
generate_oauth_hmac
(
salt
,
return_to
)
hmac
==
generate_oauth_hmac
(
salt
,
return_to
)
...
@@ -29,7 +28,9 @@ module Gitlab
...
@@ -29,7 +28,9 @@ module Gitlab
cipher
=
logout_token_cipher
(
oauth_salt
,
:encrypt
)
cipher
=
logout_token_cipher
(
oauth_salt
,
:encrypt
)
encrypted
=
cipher
.
update
(
access_token
)
+
cipher
.
final
encrypted
=
cipher
.
update
(
access_token
)
+
cipher
.
final
self
.
state
=
"
#{
oauth_salt
}
:
#{
Base64
.
urlsafe_encode64
(
encrypted
)
}
"
full_path
=
ReturnToLocation
.
new
(
return_to
).
full_path
self
.
state
=
"
#{
oauth_salt
}
:
#{
Base64
.
urlsafe_encode64
(
encrypted
)
}
:
#{
full_path
}
"
rescue
OpenSSL
::
OpenSSLError
rescue
OpenSSL
::
OpenSSLError
false
false
end
end
...
@@ -37,7 +38,8 @@ module Gitlab
...
@@ -37,7 +38,8 @@ module Gitlab
def
extract_logout_token
def
extract_logout_token
return
unless
state
.
present?
return
unless
state
.
present?
salt
,
encrypted
=
state
.
split
(
':'
,
2
)
salt
,
encrypted
,
_
=
state
.
split
(
':'
,
3
)
decipher
=
logout_token_cipher
(
salt
,
:decrypt
)
decipher
=
logout_token_cipher
(
salt
,
:decrypt
)
decipher
.
update
(
Base64
.
urlsafe_decode64
(
encrypted
))
+
decipher
.
final
decipher
.
update
(
Base64
.
urlsafe_decode64
(
encrypted
))
+
decipher
.
final
rescue
OpenSSL
::
OpenSSLError
rescue
OpenSSL
::
OpenSSLError
...
@@ -48,6 +50,10 @@ module Gitlab
...
@@ -48,6 +50,10 @@ module Gitlab
state
.
split
(
':'
,
3
)[
2
]
if
state
state
.
split
(
':'
,
3
)[
2
]
if
state
end
end
def
get_oauth_state_return_to_full_path
ReturnToLocation
.
new
(
get_oauth_state_return_to
).
full_path
end
def
authorize_url
(
params
=
{})
def
authorize_url
(
params
=
{})
oauth_client
.
auth_code
.
authorize_url
(
params
)
oauth_client
.
auth_code
.
authorize_url
(
params
)
end
end
...
@@ -65,6 +71,26 @@ module Gitlab
...
@@ -65,6 +71,26 @@ module Gitlab
private
private
class
ReturnToLocation
<
Struct
.
new
(
:location
)
def
full_path
uri
=
parse_uri
(
location
)
full_path_for_uri
(
uri
)
if
uri
end
private
def
parse_uri
(
location
)
location
&&
URI
.
parse
(
location
)
rescue
URI
::
InvalidURIError
nil
end
def
full_path_for_uri
(
uri
)
path_with_query
=
[
uri
.
path
,
uri
.
query
].
compact
.
join
(
'?'
)
[
path_with_query
,
uri
.
fragment
].
compact
.
join
(
"#"
)
end
end
def
generate_oauth_hmac
(
salt
,
return_to
)
def
generate_oauth_hmac
(
salt
,
return_to
)
return
false
unless
return_to
return
false
unless
return_to
...
...
ee/spec/controllers/oauth/geo_auth_controller_spec.rb
View file @
e6159431
...
@@ -2,9 +2,11 @@ require 'spec_helper'
...
@@ -2,9 +2,11 @@ require 'spec_helper'
describe
Oauth
::
GeoAuthController
do
describe
Oauth
::
GeoAuthController
do
let
(
:user
)
{
create
(
:user
)
}
let
(
:user
)
{
create
(
:user
)
}
let
(
:oauth_app
)
{
create
(
:doorkeeper_application
)
}
let
(
:node
)
{
create
(
:geo_node
)
}
let
(
:oauth_app
)
{
node
.
oauth_application
}
let
(
:access_token
)
{
create
(
:doorkeeper_access_token
,
resource_owner_id:
user
.
id
,
application:
oauth_app
)
}
let
(
:access_token
)
{
create
(
:doorkeeper_access_token
,
resource_owner_id:
user
.
id
,
application:
oauth_app
)
}
let
(
:auth_state
)
{
Gitlab
::
Geo
::
OauthSession
.
new
(
access_token:
access_token
.
token
,
return_to:
projects_url
).
generate_oauth_state
}
let
(
:oauth_session
)
{
Gitlab
::
Geo
::
OauthSession
.
new
(
access_token:
access_token
.
token
,
return_to:
projects_url
)
}
let
(
:auth_state
)
{
oauth_session
.
generate_oauth_state
}
let
(
:primary_node_url
)
{
'http://localhost:3001/'
}
let
(
:primary_node_url
)
{
'http://localhost:3001/'
}
before
do
before
do
...
@@ -30,7 +32,8 @@ describe Oauth::GeoAuthController do
...
@@ -30,7 +32,8 @@ describe Oauth::GeoAuthController do
end
end
describe
'GET callback'
do
describe
'GET callback'
do
let
(
:callback_state
)
{
Gitlab
::
Geo
::
OauthSession
.
new
(
access_token:
access_token
.
token
,
return_to:
projects_url
).
generate_oauth_state
}
let
(
:oauth_session
)
{
Gitlab
::
Geo
::
OauthSession
.
new
(
access_token:
access_token
.
token
,
return_to:
projects_url
)
}
let
(
:callback_state
)
{
oauth_session
.
generate_oauth_state
}
let
(
:primary_node_oauth_endpoint
)
{
Gitlab
::
Geo
::
OauthSession
.
new
.
authorize_url
(
redirect_uri:
oauth_geo_callback_url
,
state:
callback_state
)
}
let
(
:primary_node_oauth_endpoint
)
{
Gitlab
::
Geo
::
OauthSession
.
new
.
authorize_url
(
redirect_uri:
oauth_geo_callback_url
,
state:
callback_state
)
}
context
'redirection'
do
context
'redirection'
do
...
@@ -51,6 +54,12 @@ describe Oauth::GeoAuthController do
...
@@ -51,6 +54,12 @@ describe Oauth::GeoAuthController do
expect
(
response
).
to
redirect_to
(
projects_url
)
expect
(
response
).
to
redirect_to
(
projects_url
)
end
end
it
'does not display a flash message if state is valid'
do
get
:callback
,
state:
callback_state
expect
(
controller
).
to
set_flash
[
:alert
].
to
(
nil
)
end
end
end
context
'invalid credentials'
do
context
'invalid credentials'
do
...
...
ee/spec/lib/gitlab/geo/oauth_session_spec.rb
View file @
e6159431
...
@@ -52,31 +52,62 @@ describe Gitlab::Geo::OauthSession do
...
@@ -52,31 +52,62 @@ describe Gitlab::Geo::OauthSession do
end
end
describe
'#get_oauth_state_return_to'
do
describe
'#get_oauth_state_return_to'
do
subject
{
described_class
.
new
(
state:
valid_state
)
}
it
'returns return_to value'
do
it
'returns return_to value'
do
subject
=
described_class
.
new
(
state:
valid_state
)
expect
(
subject
.
get_oauth_state_return_to
).
to
eq
(
oauth_return_to
)
expect
(
subject
.
get_oauth_state_return_to
).
to
eq
(
oauth_return_to
)
end
end
end
end
describe
'#get_oauth_state_return_to_full_path'
do
it
'removes the domain from return_to value'
do
subject
=
described_class
.
new
(
state:
valid_state
)
expect
(
subject
.
get_oauth_state_return_to_full_path
).
to
eq
(
'/oauth/geo/callback'
)
end
end
describe
'#generate_logout_state'
do
describe
'#generate_logout_state'
do
it
'returns nil when access_token is not defined'
do
it
'returns nil when access_token is not defined'
do
expect
(
described_class
.
new
.
generate_logout_state
).
to
be_nil
expect
(
described_class
.
new
.
generate_logout_state
).
to
be_nil
end
end
it
'returns false when encryptation fails'
do
it
'returns false when encryptation fails'
do
allow_any_instance_of
(
OpenSSL
::
Cipher
::
AES
).
to
receive
(
:final
)
{
raise
OpenSSL
::
OpenSSLError
}
allow_any_instance_of
(
OpenSSL
::
Cipher
::
AES
)
.
to
receive
(
:final
)
{
raise
OpenSSL
::
OpenSSLError
}
expect
(
subject
.
generate_logout_state
).
to
be_falsey
expect
(
subject
.
generate_logout_state
).
to
be_falsey
end
end
it
'returns a string with salt and encrypted access token colon separated'
do
it
'returns a string with salt, encrypted access token, and return_to colon separated'
do
state
=
described_class
.
new
(
access_token:
access_token
).
generate_logout_state
subject
=
described_class
.
new
(
access_token:
access_token
,
return_to:
oauth_return_to
)
state
=
subject
.
generate_logout_state
expect
(
state
).
to
be_a
String
expect
(
state
).
to
be_a
String
expect
(
state
).
not_to
be_blank
expect
(
state
).
not_to
be_blank
salt
,
encrypted
=
state
.
split
(
':'
,
2
)
salt
,
encrypted
,
return_to
=
state
.
split
(
':'
,
3
)
expect
(
salt
).
not_to
be_blank
expect
(
salt
).
not_to
be_blank
expect
(
encrypted
).
not_to
be_blank
expect
(
encrypted
).
not_to
be_blank
expect
(
return_to
).
not_to
be_blank
end
it
'include a empty value for return_to into state when return_to param is not defined'
do
subject
=
described_class
.
new
(
access_token:
access_token
)
state
=
subject
.
generate_logout_state
_
,
_
,
return_to
=
state
.
split
(
':'
,
3
)
expect
(
return_to
).
to
eq
''
end
it
'does not include the host from return_to param into into the state'
do
subject
=
described_class
.
new
(
access_token:
access_token
,
return_to:
oauth_return_to
)
state
=
subject
.
generate_logout_state
_
,
_
,
return_to
=
state
.
split
(
':'
,
3
)
expect
(
return_to
).
to
eq
'/oauth/geo/callback'
end
end
end
end
...
@@ -94,8 +125,8 @@ describe Gitlab::Geo::OauthSession do
...
@@ -94,8 +125,8 @@ describe Gitlab::Geo::OauthSession do
end
end
it
'returns false when decryptation fails'
do
it
'returns false when decryptation fails'
do
subject
.
generate_logout_state
allow_any_instance_of
(
OpenSSL
::
Cipher
::
AES
)
allow_any_instance_of
(
OpenSSL
::
Cipher
::
AES
)
.
to
receive
(
:final
)
{
raise
OpenSSL
::
OpenSSLError
}
.
to
receive
(
:final
)
{
raise
OpenSSL
::
OpenSSLError
}
expect
(
subject
.
extract_logout_token
).
to
be_falsey
expect
(
subject
.
extract_logout_token
).
to
be_falsey
end
end
...
...
ee/spec/services/oauth2/logout_token_validation_service_spec.rb
View file @
e6159431
...
@@ -4,7 +4,8 @@ describe Oauth2::LogoutTokenValidationService do
...
@@ -4,7 +4,8 @@ describe Oauth2::LogoutTokenValidationService do
let
(
:user
)
{
create
(
:user
)
}
let
(
:user
)
{
create
(
:user
)
}
let
(
:node
)
{
create
(
:geo_node
)
}
let
(
:node
)
{
create
(
:geo_node
)
}
let
(
:access_token
)
{
create
(
:doorkeeper_access_token
,
resource_owner_id:
user
.
id
,
application_id:
node
.
oauth_application_id
)
}
let
(
:access_token
)
{
create
(
:doorkeeper_access_token
,
resource_owner_id:
user
.
id
,
application_id:
node
.
oauth_application_id
)
}
let
(
:oauth_session
)
{
Gitlab
::
Geo
::
OauthSession
.
new
(
access_token:
access_token
.
token
)
}
let
(
:oauth_return_to
)
{
'/project/test'
}
let
(
:oauth_session
)
{
Gitlab
::
Geo
::
OauthSession
.
new
(
access_token:
access_token
.
token
,
return_to:
oauth_return_to
)
}
let
(
:logout_state
)
{
oauth_session
.
generate_logout_state
}
let
(
:logout_state
)
{
oauth_session
.
generate_logout_state
}
context
'#execute'
do
context
'#execute'
do
...
@@ -15,30 +16,73 @@ describe Oauth2::LogoutTokenValidationService do
...
@@ -15,30 +16,73 @@ describe Oauth2::LogoutTokenValidationService do
end
end
it
'returns error when state param is nil'
do
it
'returns error when state param is nil'
do
result
=
described_class
.
new
(
user
,
{
state:
nil
}
).
execute
result
=
described_class
.
new
(
user
,
state:
nil
).
execute
expect
(
result
[
:status
]).
to
eq
(
:error
)
expect
(
result
[
:status
]).
to
eq
(
:error
)
end
end
it
'returns error when state param is empty'
do
it
'returns error when state param is empty'
do
result
=
described_class
.
new
(
user
,
{
state:
''
}
).
execute
result
=
described_class
.
new
(
user
,
state:
''
).
execute
expect
(
result
[
:status
]).
to
eq
(
:error
)
expect
(
result
[
:status
]).
to
eq
(
:error
)
end
end
it
'returns error when incorrect encoding'
do
it
'returns error when incorrect encoding'
do
invalid_token
=
"
\xD8
00
\xD8
01
\xD8
02"
allow_any_instance_of
(
Gitlab
::
Geo
::
OauthSession
)
allow_any_instance_of
(
Gitlab
::
Geo
::
OauthSession
).
to
receive
(
:extract_logout_token
)
{
invalid_token
}
.
to
receive
(
:extract_logout_token
)
.
and_return
(
"
\xD8
00
\xD8
01
\xD8
02"
)
result
=
described_class
.
new
(
user
,
{
state:
logout_state
}
).
execute
result
=
described_class
.
new
(
user
,
state:
logout_state
).
execute
expect
(
result
[
:status
]).
to
eq
(
:error
)
expect
(
result
[
:status
]).
to
eq
(
:error
)
end
end
it
'returns success when token is valid'
do
context
'when token is valid'
do
result
=
described_class
.
new
(
user
,
{
state:
logout_state
}).
execute
it
'returns success'
do
result
=
described_class
.
new
(
user
,
state:
logout_state
).
execute
expect
(
result
).
to
eq
(
status: :success
,
return_to:
node
.
url
)
expect
(
result
).
to
include
(
status: :success
)
end
context
'when OAuth session return_to param is nil'
do
it
'returns the Geo node URL associated with OAuth application to redirect the user back'
do
oauth_session
=
Gitlab
::
Geo
::
OauthSession
.
new
(
access_token:
access_token
.
token
,
return_to:
nil
)
logout_state
=
oauth_session
.
generate_logout_state
result
=
described_class
.
new
(
user
,
state:
logout_state
).
execute
expect
(
result
).
to
include
(
return_to:
node
.
url
)
end
end
context
'when OAuth session return_to param is empty'
do
it
'returns the Geo node URL associated with OAuth application to redirect the user back'
do
oauth_session
=
Gitlab
::
Geo
::
OauthSession
.
new
(
access_token:
access_token
.
token
,
return_to:
''
)
logout_state
=
oauth_session
.
generate_logout_state
result
=
described_class
.
new
(
user
,
state:
logout_state
).
execute
expect
(
result
).
to
include
(
return_to:
node
.
url
)
end
end
context
'when OAuth session return_to param is set'
do
it
'returns the fullpath to the Geo node to redirect the user back'
do
result
=
described_class
.
new
(
user
,
state:
logout_state
).
execute
expect
(
result
).
to
include
(
return_to:
URI
.
join
(
node
.
url
,
oauth_return_to
).
to_s
)
end
it
'replaces the host with the Geo node associated with OAuth application'
do
oauth_return_to
=
'http://fake-secondary/project/test'
oauth_session
=
Gitlab
::
Geo
::
OauthSession
.
new
(
access_token:
access_token
.
token
,
return_to:
oauth_return_to
)
logout_state
=
oauth_session
.
generate_logout_state
result
=
described_class
.
new
(
user
,
state:
logout_state
).
execute
expect
(
result
).
to
include
(
return_to:
URI
.
join
(
node
.
url
,
'/project/test'
).
to_s
)
end
end
end
end
end
end
end
end
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment