1# frozen_string_literal: true 2 3module Mattermost 4 class NoSessionError < ::Mattermost::Error 5 def message 6 'No session could be set up, is Mattermost configured with Single Sign On?' 7 end 8 end 9 10 ConnectionError = Class.new(::Mattermost::Error) 11 12 # This class' prime objective is to obtain a session token on a Mattermost 13 # instance with SSO configured where this GitLab instance is the provider. 14 # 15 # The process depends on OAuth, but skips a step in the authentication cycle. 16 # For example, usually a user would click the 'login in GitLab' button on 17 # Mattermost, which would yield a 302 status code and redirects you to GitLab 18 # to approve the use of your account on Mattermost. Which would trigger a 19 # callback so Mattermost knows this request is approved and gets the required 20 # data to create the user account etc. 21 # 22 # This class however skips the button click, and also the approval phase to 23 # speed up the process and keep it without manual action and get a session 24 # going. 25 class Session 26 include Doorkeeper::Helpers::Controller 27 28 LEASE_TIMEOUT = 60 29 30 attr_accessor :current_resource_owner, :token, :base_uri 31 32 def initialize(current_user) 33 @current_resource_owner = current_user 34 @base_uri = Settings.mattermost.host 35 end 36 37 def with_session 38 with_lease do 39 create 40 41 begin 42 yield self 43 rescue Errno::ECONNREFUSED => e 44 Gitlab::AppLogger.error(e.message + "\n" + e.backtrace.join("\n")) 45 raise ::Mattermost::NoSessionError 46 ensure 47 destroy 48 end 49 end 50 end 51 52 # Next methods are needed for Doorkeeper 53 def pre_auth 54 @pre_auth ||= Doorkeeper::OAuth::PreAuthorization.new( 55 Doorkeeper.configuration, params) 56 end 57 58 def authorization 59 @authorization ||= strategy.request 60 end 61 62 def strategy 63 @strategy ||= server.authorization_request(pre_auth.response_type) 64 end 65 66 def request 67 @request ||= OpenStruct.new(parameters: params) 68 end 69 70 def params 71 Rack::Utils.parse_query(oauth_uri.query).symbolize_keys 72 end 73 74 def get(path, options = {}) 75 handle_exceptions do 76 Gitlab::HTTP.get(path, build_options(options)) 77 end 78 end 79 80 def post(path, options = {}) 81 handle_exceptions do 82 Gitlab::HTTP.post(path, build_options(options)) 83 end 84 end 85 86 def delete(path, options = {}) 87 handle_exceptions do 88 Gitlab::HTTP.delete(path, build_options(options)) 89 end 90 end 91 92 private 93 94 def build_options(options) 95 options.tap do |hash| 96 hash[:headers] = @headers 97 hash[:allow_local_requests] = true 98 hash[:base_uri] = base_uri if base_uri.presence 99 end 100 end 101 102 def create 103 raise ::Mattermost::NoSessionError unless oauth_uri 104 raise ::Mattermost::NoSessionError unless token_uri 105 106 @token = request_token 107 raise ::Mattermost::NoSessionError unless @token 108 109 @headers = { 110 Authorization: "Bearer #{@token}" 111 } 112 113 @token 114 end 115 116 def destroy 117 post('/api/v4/users/logout') 118 end 119 120 def oauth_uri 121 return @oauth_uri if defined?(@oauth_uri) 122 123 @oauth_uri = nil 124 125 response = get('/oauth/gitlab/login', follow_redirects: false) 126 return unless (300...400) === response.code 127 128 redirect_uri = response.headers['location'] 129 return unless redirect_uri 130 131 oauth_cookie = parse_cookie(response) 132 @headers = { 133 Cookie: oauth_cookie.to_cookie_string 134 } 135 136 @oauth_uri = URI.parse(redirect_uri) 137 end 138 139 def token_uri 140 @token_uri ||= 141 if oauth_uri 142 authorization.authorize.redirect_uri if pre_auth.authorizable? 143 end 144 end 145 146 def request_token 147 response = get(token_uri, follow_redirects: false) 148 149 if (200...400) === response.code 150 response.headers['token'] 151 end 152 end 153 154 def with_lease 155 lease_uuid = lease_try_obtain 156 raise NoSessionError unless lease_uuid 157 158 begin 159 yield 160 ensure 161 Gitlab::ExclusiveLease.cancel(lease_key, lease_uuid) 162 end 163 end 164 165 def lease_key 166 "mattermost:session" 167 end 168 169 def lease_try_obtain 170 lease = ::Gitlab::ExclusiveLease.new(lease_key, timeout: LEASE_TIMEOUT) 171 lease.try_obtain 172 end 173 174 def handle_exceptions 175 yield 176 rescue Gitlab::HTTP::Error => e 177 raise ::Mattermost::ConnectionError, e.message 178 rescue Errno::ECONNREFUSED => e 179 raise ::Mattermost::ConnectionError, e.message 180 end 181 182 def parse_cookie(response) 183 cookie_hash = Gitlab::HTTP::CookieHash.new 184 response.get_fields('Set-Cookie').each { |c| cookie_hash.add_cookies(c) } 185 cookie_hash 186 end 187 end 188end 189