resp := httptest.NewRecorder()
s.handler.ServeHTTP(resp, req)
c.Check(resp.Code, check.Equals, http.StatusFound)
- c.Check(resp.Header().Get("Location"), check.Matches, `https://0.0.0.0:1/auth/joshid\?return_to=foo&?`)
+ c.Check(resp.Header().Get("Location"), check.Matches, `https://0.0.0.0:1/auth/joshid\?return_to=%2Cfoo&?`)
}
func (s *HandlerSuite) TestValidateV1APIToken(c *check.C) {
@redirect_to = root_path
if params.has_key?(:return_to)
- return send_api_token_to(params[:return_to], user)
+ # return_to param's format is 'remote,return_to_url'. This comes from login()
+ # encoding the remote=zbbbb parameter passed by a client asking for a salted
+ # token.
+ remote, return_to_url = params[:return_to].split(',', 2)
+ if remote !~ /^[0-9a-z]{5}$/ && remote != ""
+ return send_error 'Invalid remote cluster id', status: 400
+ end
+ remote = nil if remote == ''
+ return send_api_token_to(return_to_url, user, remote)
end
redirect_to @redirect_to
end
# to save the return_to parameter (if it exists; see the application
# controller). /auth/joshid bypasses the application controller.
def login
- auth_provider = if params[:auth_provider] then "auth_provider=#{CGI.escape(params[:auth_provider])}" else "" end
-
+ if params[:remote] !~ /^[0-9a-z]{5}$/ && !params[:remote].nil?
+ return send_error 'Invalid remote cluster id', status: 400
+ end
if current_user and params[:return_to]
# Already logged in; just need to send a token to the requesting
# API client.
# FIXME: if current_user has never authorized this app before,
# ask for confirmation here!
- send_api_token_to(params[:return_to], current_user)
- elsif params[:return_to]
- redirect_to "/auth/joshid?return_to=#{CGI.escape(params[:return_to])}&#{auth_provider}"
- else
- redirect_to "/auth/joshid?#{auth_provider}"
+ return send_api_token_to(params[:return_to], current_user, params[:remote])
end
+ p = []
+ p << "auth_provider=#{CGI.escape(params[:auth_provider])}" if params[:auth_provider]
+ if params[:return_to]
+ # Encode remote param inside callback's return_to, so that we'll get it on
+ # create() after login.
+ remote_param = params[:remote].nil? ? '' : params[:remote]
+ p << "return_to=#{CGI.escape(remote_param + ',' + params[:return_to])}"
+ end
+ redirect_to "/auth/joshid?#{p.join('&')}"
end
- def send_api_token_to(callback_url, user)
+ def send_api_token_to(callback_url, user, remote=nil)
# Give the API client a token for making API calls on behalf of
# the authenticated user
find_or_create_by(url_prefix: api_client_url_prefix)
end
- api_client_auth = ApiClientAuthorization.
+ @api_client_auth = ApiClientAuthorization.
new(user: user,
api_client: @api_client,
created_by_ip_address: remote_ip,
scopes: ["all"])
- api_client_auth.save!
+ @api_client_auth.save!
if callback_url.index('?')
callback_url += '&'
else
callback_url += '?'
end
- callback_url += 'api_token=' + api_client_auth.token
+ if remote.nil?
+ token = @api_client_auth.token
+ else
+ token = @api_client_auth.salted_token(remote: remote)
+ end
+ callback_url += 'api_token=' + token
redirect_to callback_url
end
'v2/' + uuid + '/' + api_token
end
+ def salted_token(remote:)
+ if remote.nil?
+ token
+ end
+ 'v2/' + uuid + '/' + OpenSSL::HMAC.hexdigest('sha1', api_token, remote)
+ end
+
protected
def permission_to_create
assert_not_nil assigns(:api_client)
end
+ test "login with remote param returns a salted token" do
+ authorize_with :inactive
+ api_client_page = 'http://client.example.com/home'
+ remote_prefix = 'zbbbb'
+ get :login, return_to: api_client_page, remote: remote_prefix
+ assert_response :redirect
+ api_client_auth = assigns(:api_client_auth)
+ assert_not_nil api_client_auth
+ assert_includes(@response.redirect_url, 'api_token='+api_client_auth.salted_token(remote: remote_prefix))
+ end
+
+ test "login with malformed remote param returns an error" do
+ authorize_with :inactive
+ api_client_page = 'http://client.example.com/home'
+ remote_prefix = 'invalid_cluster_id'
+ get :login, return_to: api_client_page, remote: remote_prefix
+ assert_response 400
+ end
end
require 'test_helper'
class UserSessionsApiTest < ActionDispatch::IntegrationTest
- def client_url
- 'https://wb.example.com'
+ # remote prefix & return url packed into the return_to param passed around
+ # between API and SSO provider.
+ def client_url(remote: nil)
+ url = ',https://wb.example.com'
+ url = "#{remote}#{url}" unless remote.nil?
+ url
end
- def mock_auth_with(email: nil, username: nil, identity_url: nil)
+ def mock_auth_with(email: nil, username: nil, identity_url: nil, remote: nil, expected_response: :redirect)
mock = {
'provider' => 'josh_id',
'uid' => 'https://edward.example.com',
mock['info']['username'] = username unless username.nil?
mock['info']['identity_url'] = identity_url unless identity_url.nil?
post('/auth/josh_id/callback',
- {return_to: client_url},
+ {return_to: client_url(remote: remote)},
{'omniauth.auth' => mock})
- assert_response :redirect, 'Did not redirect to client with token'
+
+ errors = {
+ :redirect => 'Did not redirect to client with token',
+ 400 => 'Did not return Bad Request error',
+ }
+ assert_response expected_response, errors[expected_response]
end
test 'assign username from sso' do
test 'create new user during omniauth callback' do
mock_auth_with(email: 'edward@example.com')
- assert_equal(0, @response.redirect_url.index(client_url),
+ assert_equal(0, @response.redirect_url.index(client_url.split(',', 2)[1]),
'Redirected to wrong address after succesful login: was ' +
- @response.redirect_url + ', expected ' + client_url + '[...]')
+ @response.redirect_url + ', expected ' + client_url.split(',', 2)[1] + '[...]')
assert_not_nil(@response.redirect_url.index('api_token='),
'Expected api_token in query string of redirect url ' +
@response.redirect_url)
end
+ test 'issue salted token from omniauth callback with remote param' do
+ mock_auth_with(email: 'edward@example.com', remote: 'zbbbb')
+ api_client_auth = assigns(:api_client_auth)
+ assert_not_nil api_client_auth
+ assert_includes(@response.redirect_url, 'api_token=' + api_client_auth.salted_token(remote: 'zbbbb'))
+ end
+
+ test 'error out from omniauth callback with invalid remote param' do
+ mock_auth_with(email: 'edward@example.com', remote: 'invalid_cluster_id', expected_response: 400)
+ end
+
# Test various combinations of auto_setup configuration and email
# address provided during a new user's first session setup.
[{result: :nope, email: nil, cfg: {auto: true, repo: true, vm: true}},