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
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
include HTTParty
LEASE_TIMEOUT = 60
base_uri Settings.mattermost.host
attr_accessor :current_resource_owner, :token
def initialize(current_user)
@current_resource_owner = current_user
end
def with_session
with_lease do
raise Mattermost::NoSessionError unless create
begin
yield self
rescue Errno::ECONNREFUSED
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, server.client_via_uid, params)
end
def authorization
@authorization ||= strategy.request
end
def strategy
@strategy ||= server.authorization_request(pre_auth.response_type)
end
def request
@request ||= OpenStruct.new(parameters: params)
end
def params
Rack::Utils.parse_query(oauth_uri.query).symbolize_keys
end
def get(path, options = {})
handle_exceptions do
self.class.get(path, options.merge(headers: @headers))
end
end
def post(path, options = {})
handle_exceptions do
self.class.post(path, options.merge(headers: @headers))
end
end
private
def create
return unless oauth_uri
return unless token_uri
@token = request_token
@headers = {
Authorization: "Bearer #{@token}"
}
@token
end
def destroy
post('/api/v3/users/logout')
end
def oauth_uri
return @oauth_uri if defined?(@oauth_uri)
@oauth_uri = nil
response = get("/api/v3/oauth/gitlab/login", follow_redirects: false)
return unless 300 <= response.code && response.code < 400
redirect_uri = response.headers['location']
return unless redirect_uri
@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 <= response.code && response.code < 400
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 HTTParty::Error => e
raise Mattermost::ConnectionError.new(e.message)
rescue Errno::ECONNREFUSED => e
raise Mattermost::ConnectionError.new(e.message)
end
end
end