BigW Consortium Gitlab

session.rb 3.89 KB
Newer Older
1
module Mattermost
Kamil Trzcinski committed
2
  class NoSessionError < Mattermost::Error
Kamil Trzcinski committed
3
    def message
Kamil Trzcinski committed
4
      'No session could be set up, is Mattermost configured with Single Sign On?'
Kamil Trzcinski committed
5 6 7
    end
  end

8
  ConnectionError = Class.new(Mattermost::Error)
9

10 11 12 13 14 15 16 17 18 19 20 21 22
  # 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.
23
  class Session
24 25 26
    include Doorkeeper::Helpers::Controller
    include HTTParty

27 28
    LEASE_TIMEOUT = 60

29
    base_uri Settings.mattermost.host
30

31
    attr_accessor :current_resource_owner, :token
32

33
    def initialize(current_user)
34 35 36 37
      @current_resource_owner = current_user
    end

    def with_session
38
      with_lease do
Kamil Trzcinski committed
39
        raise Mattermost::NoSessionError unless create
40 41 42 43

        begin
          yield self
        rescue Errno::ECONNREFUSED
Kamil Trzcinski committed
44
          raise Mattermost::NoSessionError
45 46 47
        ensure
          destroy
        end
48
      end
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
    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
Kamil Trzcinski committed
70 71 72 73
      Rack::Utils.parse_query(oauth_uri.query).symbolize_keys
    end

    def get(path, options = {})
Kamil Trzcinski committed
74 75 76
      handle_exceptions do
        self.class.get(path, options.merge(headers: @headers))
      end
Kamil Trzcinski committed
77 78 79
    end

    def post(path, options = {})
Kamil Trzcinski committed
80 81 82
      handle_exceptions do
        self.class.post(path, options.merge(headers: @headers))
      end
83 84 85 86 87 88 89 90
    end

    private

    def create
      return unless oauth_uri
      return unless token_uri

Kamil Trzcinski committed
91
      @token = request_token
92
      @headers = {
Kamil Trzcinski committed
93
        Authorization: "Bearer #{@token}"
94
      }
95

Kamil Trzcinski committed
96
      @token
97 98 99
    end

    def destroy
100
      post('/api/v3/users/logout')
101 102 103
    end

    def oauth_uri
Kamil Trzcinski committed
104 105 106 107
      return @oauth_uri if defined?(@oauth_uri)

      @oauth_uri = nil

108
      response = get("/api/v3/oauth/gitlab/login", follow_redirects: false)
109 110 111 112 113
      return unless 300 <= response.code && response.code < 400

      redirect_uri = response.headers['location']
      return unless redirect_uri

Kamil Trzcinski committed
114
      @oauth_uri = URI.parse(redirect_uri)
115 116 117
    end

    def token_uri
118
      @token_uri ||=
Kamil Trzcinski committed
119
        if oauth_uri
120 121
          authorization.authorize.redirect_uri if pre_auth.authorizable?
        end
122 123 124
    end

    def request_token
Kamil Trzcinski committed
125
      response = get(token_uri, follow_redirects: false)
126

127 128 129
      if 200 <= response.code && response.code < 400
        response.headers['token']
      end
130
    end
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150

    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
Kamil Trzcinski committed
151 152 153 154 155

    def handle_exceptions
      yield
    rescue HTTParty::Error => e
      raise Mattermost::ConnectionError.new(e.message)
156
    rescue Errno::ECONNREFUSED => e
Kamil Trzcinski committed
157 158
      raise Mattermost::ConnectionError.new(e.message)
    end
159
  end
160
end