2018-10-08 10:50:39 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2016-12-12 03:31:48 -05:00
|
|
|
module Mattermost
|
2021-05-31 23:10:06 -04:00
|
|
|
class NoSessionError < ::Mattermost::Error
|
2016-12-19 17:53:19 -05:00
|
|
|
def message
|
2016-12-20 13:11:53 -05:00
|
|
|
'No session could be set up, is Mattermost configured with Single Sign On?'
|
2016-12-19 17:53:19 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-05-31 23:10:06 -04:00
|
|
|
ConnectionError = Class.new(::Mattermost::Error)
|
2016-12-20 06:02:37 -05:00
|
|
|
|
2016-12-12 03:31:48 -05:00
|
|
|
# This class' prime objective is to obtain a session token on a Mattermost
|
|
|
|
# instance with SSO configured where this GitLab instance is the provider.
|
|
|
|
#
|
|
|
|
# The process depends on OAuth, but skips a step in the authentication cycle.
|
|
|
|
# For example, usually a user would click the 'login in GitLab' button on
|
|
|
|
# Mattermost, which would yield a 302 status code and redirects you to GitLab
|
|
|
|
# to approve the use of your account on Mattermost. Which would trigger a
|
|
|
|
# callback so Mattermost knows this request is approved and gets the required
|
|
|
|
# data to create the user account etc.
|
|
|
|
#
|
|
|
|
# This class however skips the button click, and also the approval phase to
|
|
|
|
# speed up the process and keep it without manual action and get a session
|
|
|
|
# going.
|
2016-12-15 08:32:50 -05:00
|
|
|
class Session
|
2016-12-12 03:31:48 -05:00
|
|
|
include Doorkeeper::Helpers::Controller
|
|
|
|
|
2016-12-20 14:01:48 -05:00
|
|
|
LEASE_TIMEOUT = 60
|
|
|
|
|
2022-03-22 08:07:28 -04:00
|
|
|
Request = Struct.new(:parameters, keyword_init: true) do
|
|
|
|
def method_missing(method_name, *args, &block)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-03-13 18:38:25 -04:00
|
|
|
attr_accessor :current_resource_owner, :token, :base_uri
|
2016-12-12 03:31:48 -05:00
|
|
|
|
2016-12-16 06:20:42 -05:00
|
|
|
def initialize(current_user)
|
2016-12-12 03:31:48 -05:00
|
|
|
@current_resource_owner = current_user
|
2018-03-13 18:38:25 -04:00
|
|
|
@base_uri = Settings.mattermost.host
|
2016-12-12 03:31:48 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def with_session
|
2016-12-20 14:01:48 -05:00
|
|
|
with_lease do
|
2017-08-01 09:04:35 -04:00
|
|
|
create
|
2016-12-20 14:01:48 -05:00
|
|
|
|
|
|
|
begin
|
|
|
|
yield self
|
2017-08-01 09:04:35 -04:00
|
|
|
rescue Errno::ECONNREFUSED => e
|
2020-09-08 05:08:31 -04:00
|
|
|
Gitlab::AppLogger.error(e.message + "\n" + e.backtrace.join("\n"))
|
2021-05-31 23:10:06 -04:00
|
|
|
raise ::Mattermost::NoSessionError
|
2016-12-20 14:01:48 -05:00
|
|
|
ensure
|
|
|
|
destroy
|
|
|
|
end
|
2016-12-15 15:06:17 -05:00
|
|
|
end
|
2016-12-12 03:31:48 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
# Next methods are needed for Doorkeeper
|
|
|
|
def pre_auth
|
|
|
|
@pre_auth ||= Doorkeeper::OAuth::PreAuthorization.new(
|
2020-09-02 14:10:40 -04:00
|
|
|
Doorkeeper.configuration, params)
|
2016-12-12 03:31:48 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def authorization
|
|
|
|
@authorization ||= strategy.request
|
|
|
|
end
|
|
|
|
|
|
|
|
def strategy
|
|
|
|
@strategy ||= server.authorization_request(pre_auth.response_type)
|
|
|
|
end
|
|
|
|
|
|
|
|
def request
|
2022-03-22 08:07:28 -04:00
|
|
|
@request ||= Request.new(parameters: params)
|
2016-12-12 03:31:48 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def params
|
2016-12-16 07:43:01 -05:00
|
|
|
Rack::Utils.parse_query(oauth_uri.query).symbolize_keys
|
|
|
|
end
|
|
|
|
|
|
|
|
def get(path, options = {})
|
2016-12-21 05:53:44 -05:00
|
|
|
handle_exceptions do
|
2018-03-13 18:38:25 -04:00
|
|
|
Gitlab::HTTP.get(path, build_options(options))
|
2016-12-21 05:53:44 -05:00
|
|
|
end
|
2016-12-16 07:43:01 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def post(path, options = {})
|
2016-12-21 05:53:44 -05:00
|
|
|
handle_exceptions do
|
2018-03-13 18:38:25 -04:00
|
|
|
Gitlab::HTTP.post(path, build_options(options))
|
2018-03-08 04:25:10 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def delete(path, options = {})
|
|
|
|
handle_exceptions do
|
2018-03-21 15:35:42 -04:00
|
|
|
Gitlab::HTTP.delete(path, build_options(options))
|
2016-12-21 05:53:44 -05:00
|
|
|
end
|
2016-12-12 03:31:48 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2018-03-13 18:38:25 -04:00
|
|
|
def build_options(options)
|
|
|
|
options.tap do |hash|
|
|
|
|
hash[:headers] = @headers
|
|
|
|
hash[:allow_local_requests] = true
|
|
|
|
hash[:base_uri] = base_uri if base_uri.presence
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-12-12 03:31:48 -05:00
|
|
|
def create
|
2021-05-31 23:10:06 -04:00
|
|
|
raise ::Mattermost::NoSessionError unless oauth_uri
|
|
|
|
raise ::Mattermost::NoSessionError unless token_uri
|
2016-12-12 03:31:48 -05:00
|
|
|
|
2016-12-16 07:43:01 -05:00
|
|
|
@token = request_token
|
2021-05-31 23:10:06 -04:00
|
|
|
raise ::Mattermost::NoSessionError unless @token
|
2017-08-01 09:04:35 -04:00
|
|
|
|
2016-12-16 06:20:42 -05:00
|
|
|
@headers = {
|
2016-12-16 07:43:01 -05:00
|
|
|
Authorization: "Bearer #{@token}"
|
2016-12-16 06:20:42 -05:00
|
|
|
}
|
2016-12-12 03:31:48 -05:00
|
|
|
|
2016-12-16 07:43:01 -05:00
|
|
|
@token
|
2016-12-12 03:31:48 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def destroy
|
2018-05-15 11:13:49 -04:00
|
|
|
post('/api/v4/users/logout')
|
2016-12-12 03:31:48 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def oauth_uri
|
2016-12-16 07:43:01 -05:00
|
|
|
return @oauth_uri if defined?(@oauth_uri)
|
|
|
|
|
|
|
|
@oauth_uri = nil
|
|
|
|
|
2019-05-14 14:38:32 -04:00
|
|
|
response = get('/oauth/gitlab/login', follow_redirects: false)
|
2017-08-01 09:04:35 -04:00
|
|
|
return unless (300...400) === response.code
|
2016-12-12 03:31:48 -05:00
|
|
|
|
|
|
|
redirect_uri = response.headers['location']
|
|
|
|
return unless redirect_uri
|
|
|
|
|
2017-08-01 09:04:35 -04:00
|
|
|
oauth_cookie = parse_cookie(response)
|
|
|
|
@headers = {
|
|
|
|
Cookie: oauth_cookie.to_cookie_string
|
|
|
|
}
|
|
|
|
|
2016-12-16 07:43:01 -05:00
|
|
|
@oauth_uri = URI.parse(redirect_uri)
|
2016-12-12 03:31:48 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def token_uri
|
2016-12-16 05:31:26 -05:00
|
|
|
@token_uri ||=
|
2016-12-16 07:43:01 -05:00
|
|
|
if oauth_uri
|
2016-12-16 05:31:26 -05:00
|
|
|
authorization.authorize.redirect_uri if pre_auth.authorizable?
|
|
|
|
end
|
2016-12-12 03:31:48 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def request_token
|
2016-12-16 07:43:01 -05:00
|
|
|
response = get(token_uri, follow_redirects: false)
|
2016-12-12 03:31:48 -05:00
|
|
|
|
2017-08-01 09:04:35 -04:00
|
|
|
if (200...400) === response.code
|
2016-12-16 05:31:26 -05:00
|
|
|
response.headers['token']
|
|
|
|
end
|
2016-12-13 13:52:41 -05:00
|
|
|
end
|
2016-12-20 14:01:48 -05:00
|
|
|
|
|
|
|
def with_lease
|
|
|
|
lease_uuid = lease_try_obtain
|
|
|
|
raise NoSessionError unless lease_uuid
|
|
|
|
|
|
|
|
begin
|
|
|
|
yield
|
|
|
|
ensure
|
|
|
|
Gitlab::ExclusiveLease.cancel(lease_key, lease_uuid)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def lease_key
|
|
|
|
"mattermost:session"
|
|
|
|
end
|
|
|
|
|
|
|
|
def lease_try_obtain
|
|
|
|
lease = ::Gitlab::ExclusiveLease.new(lease_key, timeout: LEASE_TIMEOUT)
|
|
|
|
lease.try_obtain
|
|
|
|
end
|
2016-12-21 05:53:44 -05:00
|
|
|
|
|
|
|
def handle_exceptions
|
|
|
|
yield
|
2018-03-13 18:38:25 -04:00
|
|
|
rescue Gitlab::HTTP::Error => e
|
2021-05-31 23:10:06 -04:00
|
|
|
raise ::Mattermost::ConnectionError, e.message
|
2017-02-02 09:04:02 -05:00
|
|
|
rescue Errno::ECONNREFUSED => e
|
2021-05-31 23:10:06 -04:00
|
|
|
raise ::Mattermost::ConnectionError, e.message
|
2016-12-21 05:53:44 -05:00
|
|
|
end
|
2017-08-01 09:04:35 -04:00
|
|
|
|
|
|
|
def parse_cookie(response)
|
2018-03-13 18:38:25 -04:00
|
|
|
cookie_hash = Gitlab::HTTP::CookieHash.new
|
2017-08-01 09:04:35 -04:00
|
|
|
response.get_fields('Set-Cookie').each { |c| cookie_hash.add_cookies(c) }
|
|
|
|
cookie_hash
|
|
|
|
end
|
2016-12-12 03:31:48 -05:00
|
|
|
end
|
2016-12-19 08:14:09 -05:00
|
|
|
end
|