1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192
|
# frozen_string_literal: true
module Mattermost
class NoSessionError < ::Mattermost::Error
def message
'No session could be set up, is Mattermost configured with Single Sign On?'
end
end
ConnectionError = Class.new(::Mattermost::Error)
# 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.
class Session
include Doorkeeper::Helpers::Controller
LEASE_TIMEOUT = 60
Request = Struct.new(:parameters, keyword_init: true) do
def method_missing(method_name, *args, &block); end
end
attr_accessor :current_resource_owner, :token, :base_uri
def initialize(current_user)
@current_resource_owner = current_user
@base_uri = Settings.mattermost.host
end
def with_session
with_lease do
create
begin
yield self
rescue Errno::ECONNREFUSED => e
Gitlab::AppLogger.error(e.message + "\n" + e.backtrace.join("\n"))
raise ::Mattermost::NoSessionError
ensure
destroy
end
end
end
# Next methods are needed for Doorkeeper
def pre_auth
@pre_auth ||= Doorkeeper::OAuth::PreAuthorization.new(
Doorkeeper.configuration, params)
end
def authorization
@authorization ||= strategy.request
end
def strategy
@strategy ||= server.authorization_request(pre_auth.response_type)
end
def request
@request ||= Request.new(parameters: params)
end
def params
Rack::Utils.parse_query(oauth_uri.query).symbolize_keys
end
def get(path, options = {})
handle_exceptions do
Gitlab::HTTP.get(path, build_options(options))
end
end
def post(path, options = {})
handle_exceptions do
Gitlab::HTTP.post(path, build_options(options))
end
end
def delete(path, options = {})
handle_exceptions do
Gitlab::HTTP.delete(path, build_options(options))
end
end
private
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
def create
raise ::Mattermost::NoSessionError unless oauth_uri
raise ::Mattermost::NoSessionError unless token_uri
@token = request_token
raise ::Mattermost::NoSessionError unless @token
@headers = {
Authorization: "Bearer #{@token}"
}
@token
end
def destroy
post('/api/v4/users/logout')
end
def oauth_uri
return @oauth_uri if defined?(@oauth_uri)
@oauth_uri = nil
response = get('/oauth/gitlab/login', follow_redirects: false)
return unless (300...400) === response.code
redirect_uri = response.headers['location']
return unless redirect_uri
oauth_cookie = parse_cookie(response)
@headers = {
Cookie: oauth_cookie.to_cookie_string
}
@oauth_uri = URI.parse(redirect_uri)
end
def token_uri
@token_uri ||=
if oauth_uri
authorization.authorize.redirect_uri if pre_auth.authorizable?
end
end
def request_token
response = get(token_uri, follow_redirects: false)
if (200...400) === response.code
response.headers['token']
end
end
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
def handle_exceptions
yield
rescue Gitlab::HTTP::Error => e
raise ::Mattermost::ConnectionError, e.message
rescue Errno::ECONNREFUSED => e
raise ::Mattermost::ConnectionError, e.message
end
def parse_cookie(response)
cookie_hash = Gitlab::HTTP::CookieHash.new
response.get_fields('Set-Cookie').each { |c| cookie_hash.add_cookies(c) }
cookie_hash
end
end
end
|