summaryrefslogtreecommitdiff
path: root/lib/mattermost/session.rb
blob: 688a79c04412c97bea41e5cbe7b83c8bf04a0a26 (plain)
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